This episode designs Redux architecture for large teams: feature-based slices, RTK Query for all server state, thunk and mutation writing standards, code review patterns, bundle size awareness with tree-shaking, error monitoring, and onboarding documentation.

All the techniques you've learned — slices, thunks, RTK Query, testing, debugging — only matter when organized into an architecture that many people can maintain in parallel. Episode 21 answers the question: what does a Redux structure look like that's safe for large teams and production-ready?
We'll set up team conventions: feature-based folders, RTK Query as the single door for server state, standards for writing thunks and mutations, and code review patterns. Then we close with deployment quality aspects: bundle size awareness, tree-shaking, error monitoring, and onboarding documentation so new members become productive quickly.
The folder structure is built per feature, not per file type. Each feature carries its own slice, selectors, components, and tests:
src/
app/ store.ts, hooks.ts, listenerMiddleware.ts
features/
auth/ authSlice.ts, authSelectors.ts, Login.tsx
posts/ postsSlice.ts, postsApi.ts, PostList.tsx
users/ usersSlice.ts, usersSelectors.ts
lib/ utilitas, baseQuery custom
test/ test store, MSW handlersThe golden rule: one self-contained feature is changed, reviewed, and tested without touching other features. A change in features/auth shouldn't force anyone to open features/posts. You also recognize this pattern from the combineSlices discussion in episode 11.
The convention: all data coming from the network is managed by RTK Query; manual slices only hold UI and client-only state. The benefits show in episodes 8-9 and 17 — caching, invalidation, and automatic retries don't need to be rewritten per feature:
export const usersApi = createApi({
reducerPath: "usersApi",
baseQuery: baseQueryWithAuth,
endpoints: (builder) => ({
listUsers: builder.query<User[], void>({
query: () => "users",
providesTags: (result) => [
...(result?.map((u) => ({ type: "User" as const, id: u.id })) ?? []),
{ type: "User" as const, id: "LIST" },
],
}),
}),
})With one authenticated baseQuery (episode 17), every feature gets automatic token refresh. This consistency of patterns is what makes the codebase understandable without reading each implementation.
Team standards prevent divergent coding styles. Some rules you can adopt:
1. Satu slice menangani satu domain; tambahkan slice baru, bukan
memperluas slice yang tidak relevan.
2. Gunakan createAsyncThunk untuk satu request, thunk manual untuk
alur multi-langkah (episode 12).
3. Mutation RTK Query wajib memakai tags; tidak boleh ada
refetch manual setelah mutasi.
4. Semua fetch memakai baseQuery bersama, bukan fetch langsung
di komponen.The checklist above is enforced automatically by linters when possible, and by code review when not. The result: architectural decisions aren't repeated in every PR.
Review focus for Redux code should answer specific questions:
invalidatesTags?Add these questions as a PR template. They turn review from subjective judgment into a checklist everyone can execute.
Redux Toolkit plus react-redux adds about 14KB (gzipped) to the app bundle. For apps very sensitive to size, record a baseline and monitor changes:
npx next buildWatch two things: redux and react-redux load only once (no duplication from mixed CJS/ESM), and all RTK code benefits from tree-shaking — imports like createSlice are bundled without dragging RTK Query along when it isn't used.
Tree-shaking works when imports are named imports and the bundler runs in production mode:
import { createSlice } from "@reduxjs/toolkit"
import { setupListeners } from "@reduxjs/toolkit/query"Avoid importing from @reduxjs/toolkit/query when you only use createAsyncThunk — the bundler drops unused query APIs, keeping the bundle lean.
Production bugs can't rely on the console alone. Install an error boundary to catch render errors, and send error actions to a monitoring service:
import { Component } from "react"
export class ErrorBoundary extends Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error, info) {
reportError(error, info.componentStack)
}
render() {
if (this.state.hasError) return <p>Terjadi kesalahan.</p>
return this.props.children
}
}reportError sends the trace to Sentry or a similar service. Combine it with the logging middleware from episode 16 to see the last action before a crash.
New members shouldn't have to guess the architecture. Write short documentation stating the key decisions:
This documentation is more expensive to maintain than to write — keep it short and link directly to example code, not long paragraphs that go stale quickly.
Warning
Bundle size awareness doesn't mean avoiding Redux. It means: record a baseline, keep tree-shaking, and don't import the whole library when a single slice is enough. The 14KB is a fair price for the debugging and structure you get.
Production architecture is the result of team discipline, not a single decision. Feature-based slices enable parallel work, RTK Query is the single door for server state, thunk and mutation standards unify style, and code review asks the same questions in every PR. Combined with bundle size awareness, error monitoring, and onboarding docs, your Redux architecture is ready to be maintained for years.
Key takeaways:
Episode 22 — the final episode of this series — invites you to reflect on the journey: comparing Redux with Zustand, Jotai, TanStack Query, and the Context API, building a 2026 decision framework, and closing with a recap of all material from episodes 0 to 21.