Skip to content

How to use CSS Modules with TanStack Start? #3023

Closed

Description

@calmqwe

Which project does this relate to?

Start

Describe the bug

I'm struggling to make CSS Modules work properly. Currently, the styles are being loaded only on the client, causing a flash of unstyled content (FOUC) during initial render.
I tried in this repo https://github.com/tanstack/router/tree/main/examples/react/start-basic

Your Example Website or App

https://github.com/nikolayemrikh/start-css-modules-bug

Steps to Reproduce the Bug or Issue

  1. Clone repo https://github.com/nikolayemrikh/start-css-modules-bug
  2. Add CSS Modules support by creating a .module.css file and importing it into a React component.
  3. Run the server and open the app in a browser.
  4. Observe that the styles are applied only after the client-side hydration, causing a flash of unstyled content (FOUC).

Expected behavior

I would like to see a working example or documentation on how to properly configure TanStack Router with CSS Modules and SSR.
The desired behavior is to have styles included in the server-rendered HTML, so there is no flash of unstyled content (FOUC).

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Activity

  1. roboncode commented on Dec 21, 2024

    @roboncode

    In addition to this. on build, the CSS is not referenced by cloudflare-pages. I also ran a build using the node-server preset and it also has the same behavior.

    app.config.ts

    export default defineConfig({
      server: {
        preset: 'node-server',
        // preset: 'cloudflare-pages',
        // unenv: cloudflare,
      },
    })

    client.tsx

    import '@shopify/polaris/build/esm/styles.css'
    import './client.css'
  2. gioruu commented on Dec 21, 2024

    @gioruu

    Having the same flash of unstyled content (FOUC) problem. Waiting for a Messiah.

  3. gioruu commented on Dec 21, 2024

    @gioruu

    This worked:

    • Add a '?url' to the css import:
      import CSS from '../app.css?url'

    • Add this line to the Route object head:
      links: [{ rel: 'stylesheet', href: CSS }]

    Full code:

    import {
       Outlet,
       ScrollRestoration,
       createRootRoute,
    } from '@tanstack/react-router'
    import { Meta, Scripts } from '@tanstack/start'
    import type { ReactNode } from 'react'
    import CSS from '../app.css?url'      // this
    
    export const Route = createRootRoute({
       head: () => ({
          meta: [
             {
                charSet: 'utf-8',
             },
             {
                name: 'viewport',
                content: 'width=device-width, initial-scale=1',
             },
             {
                title: 'TanStack Start Starter',
             },
          ],
          links: [{ rel: 'stylesheet', href: CSS }]          // and this
       }),
       component: RootComponent,
    })
    
    function RootComponent() {
       return (
          <RootDocument>
             <Outlet />
          </RootDocument>
       )
    }
    
    function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
       return (
          <html>
             <head>
                <Meta />
             </head>
             <body>
                {children}
                <ScrollRestoration />
                <Scripts />
             </body>
          </html>
       )
    }

    "@tanstack/start": "^1.91.3",

  4. roboncode commented on Dec 23, 2024

    @roboncode

    Thx @gioruu. I verified this works as well.

  5. nikolayemrikh commented on Dec 23, 2024

    @nikolayemrikh

    @gioruu, your example works because you use the URL directly in the root. However, it doesn't make sense; we need to have the ability to write CSS and place it next to a component using CSS Modules. Neither CSS Modules nor imports work properly within a component.

    It works as expected in other frameworks like Next.js.

  6. nikolayemrikh commented on Dec 23, 2024

    @nikolayemrikh

    @SeanCassiere could you please reopen it? Not everyone use tailwind.

  7. roboncode commented on Dec 24, 2024

    @roboncode

    @j-mcfarlane, not sure about his repo but when i added the the ?url it worked for me and it started including it in my build

    import { AppProvider } from '@shopify/polaris'
    import POLARIS_CSS from '@shopify/polaris/build/esm/styles.css?url'
    import enTranslations from '@shopify/polaris/locales/en.json'
    import type { ReactNode } from 'react'
    import CLIENT_CSS from '../client.css?url'
    import { QueryProvider } from '../providers/QueryProvider'
    
    export const Route = createRootRoute({
      head: () => ({
        meta: [
          {
            charSet: 'utf-8',
          },
          {
            name: 'viewport',
            content: 'width=device-width, initial-scale=1',
          },
          {
            title: 'TanStack Start Starter',
          },
        ],
        links: [
          { rel: 'stylesheet', href: CLIENT_CSS },
          { rel: 'stylesheet', href: POLARIS_CSS },
        ],
      }),
      component: RootComponent,
    })
  8. IRlyDontKnow commented on Dec 24, 2024

    @IRlyDontKnow

    @j-mcfarlane, not sure about his repo but when i added the the ?url it worked for me and it started including it in my build

    import { AppProvider } from '@shopify/polaris'
    import POLARIS_CSS from '@shopify/polaris/build/esm/styles.css?url'
    import enTranslations from '@shopify/polaris/locales/en.json'
    import type { ReactNode } from 'react'
    import CLIENT_CSS from '../client.css?url'
    import { QueryProvider } from '../providers/QueryProvider'

    export const Route = createRootRoute({
    head: () => ({
    meta: [
    {
    charSet: 'utf-8',
    },
    {
    name: 'viewport',
    content: 'width=device-width, initial-scale=1',
    },
    {
    title: 'TanStack Start Starter',
    },
    ],
    links: [
    { rel: 'stylesheet', href: CLIENT_CSS },
    { rel: 'stylesheet', href: POLARIS_CSS },
    ],
    }),
    component: RootComponent,
    })

    That's because you're not using CSS Modules in your example ;)

  9. 53 remaining items

  10. antitoxic commented on Dec 1, 2025

    @antitoxic

    I previously posted in this thread. Since then, our team has been using CSS modules with TSS, so I want to try and bring some of our understanding and maybe help others fix their issue:

    • CSS modules are working, and are split per route in production.
    • CSS modules are not files during dev mode, they are style tags that are dynamically added, so you are seeing FOUC. That's normal.
    • You can see FOUC in production if you haven't configured your hosting provider (netlify, vercel, etc.) to cache the CSS files. Before we configured that, we were seeing FOUC.

    netlify.toml:

    [[headers]]
      # Files inside /assets/ use hash-based names, so we can cache them forever
      for = "/assets/*"
    
      [headers.values]
        cache-control = "public, max-age=31536000, immutable"

    .vercel/output/config.json (we use our own CI to build):

    {
      "version": 3,
      "overrides": {},
      "routes": [
        {
          "src": "/assets/(.*)",
          "headers": {
            "cache-control": "public, max-age=31536000, immutable"
          }
        },
        {
          "handle": "filesystem"
        },
        {
          "src": "/(.*)",
          "dest": "/<COPY OF dist/server INTO .vercel/output/functions where index.mjs just re-exports dist/server.js>.func"
        }
      ]
    }
    
  11. wijonja commented on Dec 1, 2025

    @wijonja

    thank you @antitoxic. i'll give that a go

  12. dunklesToast commented on Dec 1, 2025

    @dunklesToast
    • You can see FOUC in production if you haven't configured your hosting provider (netlify, vercel, etc.) to cache the CSS files. Before we configured that, we were seeing FOUC.

    But thats not really solving the issue, does it? It only ensures the files can be loaded faster and therefore have no flicker if i understand your configs correctly.

  13. antitoxic commented on Dec 2, 2025

    @antitoxic

    But thats not really solving the issue, does it? It only ensures the files can be loaded faster and therefore have no flicker if i understand your configs correctly.

    I'm sharing what helped our team. The FOUC never happened on first page load for us. It only happened when navigating from the already loaded page to another or back from a second page.

    We found out that the browser is discarding the preloaded (on hover) CSS because there was no cache settings.

    Adding the configuration above solved the issue for us. It is very easy to test out at least the netlify config and see if it works for you.

    We've never experienced a different CSS name getting generated for the server bundle and for the client bundle. Regarding this topic I'm assuming that something goes wrong with the hashing of the filename, or the file name includes random characters where it should actually be hash of the contents.

  14. trompx commented on Dec 2, 2025

    @trompx

    On my side, I have a page transition with motion AnimatePresence, that fades the previous page. The styles of the previous page are removed before the page faded out, so I get the FOUC. It's a small website so as a temporary "very disgusting" hack, I added an empty .fouc class to all the CSS modules and imported them on a Layout component common to all pages -> no more FOUC, but suboptimal styles loading.

  15. KoziKim commented on Dec 20, 2025

    @KoziKim

    I’m looking at TanStack Start for a side project, but the FOUC with CSS Modules is pretty concerning.

    Is there any update on this?

  16. schiller-manuel commented on Dec 20, 2025

    @schiller-manuel
    Collaborator

    @KoziKim the FOUC during dev you mean?

  17. ViliamKopecky commented on Dec 20, 2025

    @ViliamKopecky

    @schiller-manuel Yes, dev mode only.

    I get why that feels unimportant.

    But as a new user, this is one of the first things I hit within minutes when not using Tailwind/static CSS. It looks pretty rough, and it immediately lowers confidence that the rest of the framework is worth the try. Subjectively of course.

  18. schiller-manuel commented on Dec 20, 2025

    @schiller-manuel
    Collaborator

    we will fix this for sure, just not highest priority when compared to runtime bugs

  19. rhyek commented on Dec 20, 2025

    @rhyek
    Contributor

    we will fix this for sure, just not highest priority when compared to runtime bugs

    @schiller-manuel some comments here from multiple users are saying this is happening for them in prod. Is this not correct?

  20. schiller-manuel commented on Dec 20, 2025

    @schiller-manuel
    Collaborator

    @rhyek i havent seen a reproducer for this. do you have one?

  21. added a commit that references this issue on Jan 10, 2026
    e231ba1
  22. myamaak commented on Feb 25, 2026

    @myamaak

    #3023 (comment) helped me identify the cause of the FOUC I was experiencing in my production build — thank you!

    One thing I noticed is that FOUC can be reproduced by enabling "Disable cache" in DevTools. It doesn't occur on the initial page load, but rather during client-side navigation (e.g., navigating to another page and then pressing the back button). With cache enabled, styles load correctly without any flash.

    Even if this is the expected behavior, I think it would be worth addressing — having FOUC during navigation with cache disabled isn't a great DX and can make it difficult to distinguish between real styling issues and caching behavior.

  23. chill-czar commented on May 4, 2026

    @chill-czar

    Which project does this relate to?

    Start

    Describe the bug

    I'm struggling to make CSS Modules work properly. Currently, the styles are being loaded only on the client, causing a flash of unstyled content (FOUC) during initial render. I tried in this repo https://github.com/tanstack/router/tree/main/examples/react/start-basic

    Your Example Website or App

    https://github.com/nikolayemrikh/start-css-modules-bug

    Steps to Reproduce the Bug or Issue

    1. Clone repo https://github.com/nikolayemrikh/start-css-modules-bug
    2. Add CSS Modules support by creating a .module.css file and importing it into a React component.
    3. Run the server and open the app in a browser.
    4. Observe that the styles are applied only after the client-side hydration, causing a flash of unstyled content (FOUC).

    Expected behavior

    I would like to see a working example or documentation on how to properly configure TanStack Router with CSS Modules and SSR. The desired behavior is to have styles included in the server-rendered HTML, so there is no flash of unstyled content (FOUC).

    Screenshots or Videos

    No response

    Platform

    • OS: [e.g. macOS, Windows, Linux]
    • Browser: [e.g. Chrome, Safari, Firefox]
    • Version: [e.g. 91.1]

    Additional context

    No response

    It tooke me 48hr to fucking fix this tanstack start fouc issue

    const config = defineConfig({
      plugins: [
        devtools(),
        nitro({
          // Cache hashed assets forever — fixes FOUC during client-side navigation.
          // Without this the browser re-fetches CSS on each navigation and can
          // show unstyled content while the request is in-flight.
          // See: https://github.com/TanStack/router/issues/3023#issuecomment-2559735249
          routeRules: {
            // globals.css has no hash (stable name) — must-revalidate so browsers check for updates
            '/assets/globals.css': {
              headers: { 'cache-control': 'public, max-age=0, must-revalidate' },
            },
            // All other hashed assets can be cached forever
            '/assets/**': {
              headers: { 'cache-control': 'public, max-age=31536000, immutable' },
            },
          },
        }),
        viteTsConfigPaths({
          projects: ['./tsconfig.json'],
        }),
        tailwindcss(),
        tanstackStart(),
        viteReact({
          babel: {
            plugins: ['babel-plugin-react-compiler'],
          },
        }),
      ],
    
      build: {
        // Raise warning threshold — some vendor bundles (Mermaid, Excalidraw)
        // are intentionally large and can't be split further without dynamic import
        chunkSizeWarningLimit: 600,
        rollupOptions: {
          output: {
            manualChunks,
            // globals.css is processed twice during build (once via ?url import for SSR manifest,
            // once as a transitive dep of @blocknote/shadcn) producing two different hashes.
            // A stable filename ensures the SSR manifest URL always matches the deployed file.
            assetFileNames: (assetInfo) => {
              if (assetInfo.name === 'globals.css') {
                return 'assets/globals.css'
              }
              return 'assets/[name]-[hash][extname]'
            },
          },
        },
      },
    
    })
    
    export default config 
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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