What is preloadedState in Redux and How Does It Enable Server-Side Rendering?

preloadedState is the optional second argument to Redux's createStore function that allows you to hydrate the store with existing data, enabling seamless server-side rendering by synchronizing initial state between server and client.

In the reduxjs/redux repository, preloadedState serves as the foundation for isomorphic applications. It allows a Redux store to be initialized with data that exists before the application runs, rather than starting from an empty state. This capability is essential for server-side rendering (SSR) workflows where the server must capture application state and transmit it to the browser.

Understanding preloadedState in Redux

The preloadedState parameter is defined in src/createStore.ts at lines 90-96 as part of the createStore function signature:

export function createStore<
  S,
  A extends Action,
  Ext extends {} = {},
  StateExt extends {} = {},
  PreloadedState = S
>(
  reducer: Reducer<S, A, PreloadedState>,
  preloadedState?: PreloadedState | undefined,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, UnknownIfNonSpecific<StateExt>> & NoInfer<Ext>

When you invoke createStore(reducer, preloadedState), the Redux store initializes by calling the reducer with preloadedState instead of undefined. This bypasses the reducer's default state initialization logic and uses your provided snapshot. According to docs/usage/Structuring-Reducers/InitializingState.md, this establishes the precedence rules between a reducer's default state and the preloadedState value.

How preloadedState Works with Server-Side Rendering

Server-side rendering with Redux relies on preloadedState to prevent state mismatch between server and client. The pattern ensures the client starts with the exact state the server produced, eliminating hydration errors and providing fast time-to-interactive.

Server-Side: Creating and Serializing the Store

On the server, you fetch data needed for the specific route, create a Redux store with that data as preloadedState, render the React tree to a string, and serialize the final state. As documented in docs/usage/ServerRendering.md, you embed this serialized state in the HTML response:

// Server-side store creation
const store = createStore(rootReducer, { data: serverFetchedData });

// After rendering to string
const preloadedState = store.getState();

// Inject into HTML
res.send(`
  <script>
    window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
  </script>
`);

The JSON.stringify replacement of < characters prevents XSS attacks through state serialization.

Client-Side: Hydrating the Store

On the client, you read the serialized state from the global variable and pass it directly to createStore. According to the source in src/createStore.ts and the SSR documentation, this rehydrates the store without requiring an additional render pass:

// Read the state injected by the server
const preloadedState = window.__PRELOADED_STATE__;

// Create store with the exact server state
const store = createStore(rootReducer, preloadedState);

Code Implementation: Complete SSR Example

Server Implementation

The following Express.js implementation demonstrates the complete server-side flow using preloadedState:

// server.js
import express from 'express';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './src/reducers';
import App from './src/App';

const app = express();

app.get('*', async (req, res) => {
  // Fetch route-specific data
  const data = await fetchDataForRoute(req.path);
  
  // Create store with preloadedState
  const store = createStore(rootReducer, { data });
  
  // Render application
  const html = renderToString(
    <Provider store={store}>
      <App />
    </Provider>
  );
  
  // Serialize state for client
  const preloadedState = store.getState();
  
  res.send(`
    <!doctype html>
    <html>
      <head><title>My App</title></head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
        </script>
        <script src="/static/client.bundle.js"></script>
      </body>
    </html>
  `);
});

Client Implementation

The client entry point reads the injected state and initializes the store:

// client.js
import { createStore } from 'redux';
import rootReducer from './src/reducers';
import { Provider } from 'react-redux';
import { render } from 'react-dom';
import App from './src/App';

// Hydrate from server-provided state
const preloadedState = window.__PRELOADED_STATE__;

const store = createStore(rootReducer, preloadedState);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Best Practices and Memory Considerations

When implementing preloadedState with server-side rendering, pass window.__PRELOADED_STATE__ directly to createStore without assigning it to an intermediate variable when possible. As noted in docs/usage/ServerRendering.md at line 180, creating an extra reference keeps the large state object alive longer than necessary, preventing the garbage collector from reclaiming memory efficiently.

Additionally, always sanitize the serialized state when embedding it in HTML to prevent XSS attacks. The replacement of < with its Unicode escape sequence (\u003c) in JSON.stringify output prevents script injection through malicious state properties.

Summary

  • preloadedState is the optional second argument to createStore in src/createStore.ts that initializes the Redux store with existing data instead of starting from undefined.
  • In server-side rendering workflows, the server creates a store with fetched data, renders the application, serializes the state to window.__PRELOADED_STATE__, and embeds it in the HTML response.
  • The client reads the global state variable and passes it directly to createStore, ensuring the client starts with the exact server state and preventing hydration mismatches.
  • Pass the preloaded state directly to createStore without intermediate variables to optimize memory usage and garbage collection.

Frequently Asked Questions

What happens if preloadedState conflicts with reducer default state?

When you provide preloadedState, Redux uses that value instead of the reducer's default state on initialization. According to docs/usage/Structuring-Reducers/InitializingState.md, the preloadedState takes precedence during the initial dispatch, but subsequent actions will follow normal reducer logic. If you need to merge server state with defaults, handle that logic in your reducer or when preparing the preloadedState object.

Can preloadedState be used for purposes other than server-side rendering?

Yes, preloadedState is commonly used to restore persisted state from localStorage or sessionStorage, allowing users to resume their session after refreshing the page. It can also initialize stores with configuration data loaded at build time or hydrate state from a native mobile app's embedded JavaScript environment. Any scenario where the store needs to start with existing data rather than empty initial state can utilize this parameter.

Why does the documentation recommend passing window.PRELOADED_STATE directly?

The documentation in docs/usage/ServerRendering.md recommends passing the global variable directly to createStore to avoid creating an extra reference to the large state object. When you assign const state = window.__PRELOADED_STATE__ and then pass state to createStore, both the global variable and your local constant reference the same data, preventing the garbage collector from reclaiming that memory until both references are cleared. Passing directly allows the global reference to be deleted or overwritten sooner, optimizing memory usage in the browser.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →