Redux Core Intermediate 12 min read

Redux Store Setup 2026: Production RTK + RTK Query 🚀

Complete Redux Toolkit store configuration with TypeScript, RTK Query caching, DevTools, middleware stack, persistence, code-splitting, and enterprise patterns that scale to 100+ slices.

#redux toolkit #store #react #rtk query #typescript redux
Guide Redux Core

Redux Store Setup 2026: Production RTK Configuration 🚀

Redux Toolkit (RTK) provides zero-boilerplate store setup with TypeScript-first slices, RTK Query caching, DevTools time-travel, middleware extensibility, and automatic code-splitting. Modern stores combine client state (auth, UI) with server state (RTK Query) and support React 19 Server Components hydration.

🎯 Production Store Architecture

store/ ├── index.ts # Root store config ├── slices/ # Client state (auth, ui) │ ├── authSlice.ts │ └── uiSlice.ts ├── services/ # RTK Query APIs │ ├── authApi.ts │ └── todoApi.ts └── middleware/ # Custom middleware ├── authInterceptor.ts └── logger.ts

🚀 Step-by-Step Production Setup

Step 1: Install Production Dependencies

# Core RTK + React integration
npm install @reduxjs/toolkit react-redux

# RTK Query (server state)
npm install @reduxjs/toolkit

# DevTools + Persistence (optional)
npm install redux-persist @redux-devtools/cli
npm install -D @types/redux-persist

Step 2: Root Store Configuration

// store/index.ts - Production store
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import authSlice from './slices/authSlice';
import uiSlice from './slices/uiSlice';
import { authApi } from './services/authApi';
import { todoApi } from './services/todoApi';
import { authInterceptor } from './middleware/authInterceptor';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth'] // Only persist auth
};

const rootReducer = {
  auth: persistReducer(persistConfig, authSlice),
  ui: uiSlice,
  [authApi.reducerPath]: authApi.reducer,
  [todoApi.reducerPath]: todoApi.reducer
};

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST']
      }
    })
      .concat(authApi.middleware, todoApi.middleware)
      .concat(authInterceptor),
  devTools: process.env.NODE_ENV !== 'production',
  // Code-splitting support
  preloadedState: (window as any).__PRELOADED_STATE__
});

setupListeners(store.dispatch); // Auto-refetch on reconnect

export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Step 3: React Provider (With Persistence)

// providers/ReduxProvider.tsx
'use client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from '@/store';
import { StrictMode } from 'react';

export function ReduxProvider({ children }: { children: React.ReactNode }) {
  return (
    <Provider store={store}>
      <PersistGate loading={<Spinner />} persistor={persistor}>
        <StrictMode>{children}</StrictMode>
      </PersistGate>
    </Provider>
  );
}

Step 4: Typed Hooks

// hooks/useAppDispatch.ts
import { useDispatch } from 'react-redux';
import type { AppDispatch } from '@/store';

export const useAppDispatch = () => useDispatch<AppDispatch>();

// hooks/useAppSelector.ts
import { useSelector } from 'react-redux';
import type { RootState } from '@/store';

export const useAppSelector = <TSelected>(
  selector: (state: RootState) => TSelected
) => useSelector<RootState, TSelected>(selector);

🛠️ Example Slices + RTK Query

Auth Slice (Client State)

// store/slices/authSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { logout } from '../authActions';

interface AuthState {
  user: User | null;
  token: string | null;
}

const initialState: AuthState = {
  user: null,
  token: null
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setCredentials: (state, action: PayloadAction<{ user: User; token: string }>) => {
      state.user = action.payload.user;
      state.token = action.payload.token;
    }
  },
  extraReducers: (builder) => {
    builder.addCase(logout, (state) => {
      state.user = null;
      state.token = null;
    });
  }
});

export const { setCredentials } = authSlice.actions;
export default authSlice.reducer;

RTK Query API

// store/services/todoApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { RootState } from '../index';

export const todoApi = createApi({
  reducerPath: 'todoApi',
  baseQuery: fetchBaseQuery({
    baseUrl: '/api/',
    prepareHeaders: (headers, { getState }) => {
      const token = (getState() as RootState).auth.token;
      if (token) {
        headers.set('authorization', `Bearer ${token}`);
      }
      return headers;
    }
  }),
  tagTypes: ['Todo'],
  endpoints: (builder) => ({
    getTodos: builder.query<Todo[], void>({
      query: () => 'todos',
      providesTags: ['Todo']
    }),
    addTodo: builder.mutation<Todo, Partial<Todo>>({
      query: (todo) => ({
        url: 'todos',
        method: 'POST',
        body: todo
      }),
      invalidatesTags: ['Todo']
    })
  })
});

export const { useGetTodosQuery, useAddTodoMutation } = todoApi;

🎯 Production Features

1. Code-Splitting (Server-Side Rendering)

// next.config.js
export default {
  // Preload initial state for SSR
  generateEtags: false
};

// pages/_app.tsx
if (typeof window !== 'undefined') {
  (window as any).__PRELOADED_STATE__ = initialReduxState;
}

2. Auth Interceptor Middleware

// middleware/authInterceptor.ts
import { Middleware } from '@reduxjs/toolkit/query';
import type { RootState } from '../store';

export const authInterceptor: Middleware = (store) => (next) => (action) => {
  if (todoApi.util.resetApiState.match(action)) return next(action);
  
  const token = (store.getState() as RootState).auth.token;
  if (token && 'meta' in action && action.meta.request) {
    action.meta.request.headers = {
      ...action.meta.request.headers,
      Authorization: `Bearer ${token}`
    };
  }
  
  return next(action);
};

📊 Store Performance Metrics

FeatureVanilla ReduxRedux Toolkit
Bundle Size12KB2.1KB
Setup Time2 hours5 minutes
TypeScriptManualBuilt-in
DevToolsPluginNative
CachingManualRTK Query

🎯 Production Checklist

✅ [] configureStore with TypeScript generics ✅ [] RTK Query for server state (90% use cases) ✅ [] Persistence (redux-persist) for auth ✅ [] Auth interceptor middleware ✅ [] DevTools time-travel (non-production) ✅ [] Typed hooks (useAppDispatch/useAppSelector) ✅ [] Code-splitting support (PRELOADED_STATE) ✅ [] ESLint + Prettier configured

🚀 Complete App Integration

// app/layout.tsx (Next.js 15 App Router)
export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ReduxProvider>
          {children}
        </ReduxProvider>
      </body>
    </html>
  );
}

// components/TodoApp.tsx
function TodoApp() {
  const { data: todos = [] } = useGetTodosQuery();
  const [addTodo] = useAddTodoMutation();
  
  return (
    <div className="p-8">
      <TodoForm onSubmit={addTodo} />
      <TodoList todos={todos} />
    </div>
  );
}

🎯 Final Thoughts

Redux Toolkit store setup = 5 minutes to production. RTK Query handles server state, createSlice eliminates boilerplate, middleware adds enterprise features, and TypeScript generics ensure type safety at scale.

2026 State Architecture: Server Components → Initial data (60%) RTK Query → Cached mutations (30%) Slices → Client state (10%)

Vanilla Redux = 2018. RTK Production Store = 2026. Build enterprise React apps with predictable state and automatic caching 🚀.

Chat with us