Skip to content

ThemeProvider with Remix doesn't work #3331

@ustimenko-andrew

Description

@ustimenko-andrew

Hello Emotion team,

I'm using Remix with Emotion and have encountered a server-side rendering issue: when I wrap the app in <ThemeProvider> from @emotion/react and try to access the theme inside a styled component (or using useTheme()), the theme object is {} during SSR. On the client side, everything works fine.

I'm basing my setup on this official Remix + MUI example, with slight modifications:
https://github.com/mui/material-ui/blob/master/examples/material-ui-remix-ts/app/entry.server.tsx

Current behavior:

On the server, the theme inside Emotion-styled components is an empty object {}.

To reproduce:

Here is the relevant SSR configuration:

const handleBrowserRequest = (
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
) => {
  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);

  function MuiRemixServer() {
    return (
      <ServerStyleContext.Provider value={null}>
        <CacheProvider value={cache}>
          <ThemeProvider theme={theme}>
            <RemixServer context={remixContext} url={request.url} />
          </ThemeProvider>
        </CacheProvider>
      </ServerStyleContext.Provider>
    );
  }

  // Render the component to a string.
  const html = ReactDOMServer.renderToString(<MuiRemixServer />);
  // Grab the CSS from emotion
  const chunks = extractCriticalToChunks(html);

  const markup = ReactDOMServer.renderToString(
    <ServerStyleContext.Provider value={chunks.styles}>
      <CacheProvider value={cache}>
        <ThemeProvider theme={theme}>
          <CssBaseline />
          <RemixServer context={remixContext} url={request.url} />
        </ThemeProvider>
      </CacheProvider>
    </ServerStyleContext.Provider>,
  );

  responseHeaders.set("Content-Type", "text/html");

  // Enhanced cache control headers to prevent aggressive browser caching
  responseHeaders.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
  responseHeaders.set("Surrogate-Control", "no-store");
  responseHeaders.set("Pragma", "no-cache");
  responseHeaders.set("Expires", "0");
  // Add a unique timestamp to force browsers to refetch resources
  responseHeaders.set("X-Content-Timestamp", new Date().getTime().toString());

  return new Response(`<!DOCTYPE html>${markup}`, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
};

A simplified styled component that reproduces the issue:

import styled from "@emotion/styled";

export const StyledDiv = styled.div`
  ${({ theme }) => {
    console.log("Theme on server:", theme); // Outputs {}
    return "";
  }}
`;

Expected behavior:

The theme object provided to should be available inside styled components and useTheme() on the server, just like on the client.

Environment information:

  • react version: 18.2.0
  • @emotion/react version: 11.13.3
  • @emotion/server version: 11.11.0
  • @mui/material version: 6.1.6
  • @remix-run/dev version: 2.16.4

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions