How to use CSS Modules with TanStack Start?
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
- Clone repo https://github.com/nikolayemrikh/start-css-modules-bug
- Add CSS Modules support by creating a .module.css file and importing it into a React component.
- Run the server and open the app in a browser.
- 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
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'
Having the same flash of unstyled content (FOUC) problem. Waiting for a Messiah.
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",
Thx @gioruu. I verified this works as well.
@roboncode are you sure, I cloned this guys repo and I can clearly see both his red and green squares loading without styling and then being hydrated giving the 'flicker' effect.
I am having the same error using pigment css
@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.
@SeanCassiere could you please reopen it? Not everyone use tailwind.
I agree with @nikolayemrikh
I am trying to implement a monorepo using tanstack start. I was previously using styled-components + vite + tanstack with great success. I am shifting to tanstack start and am getting the FOUC issues on mount
https://github.com/j-mcfarlane/tanstack-start-monorepo/
I believe this is inline with this error https://github.com/nksaraf/vinxi/issues/79
How the FOUC do I fix this
@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,
})
@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 ;)
Got similar issues using TailwindCSS 4.0 with TSS as well.
update: now there's a new issue related to that. https://github.com/TanStack/router/issues/2899
Just tried @gioruu 's solution above while setting up Tailwind with TanStack Start and I got some weird issues with it. I then realized that I had a rogue service worker running in that address (using the same port 3000 for a lot of local development stuff). After i de-registered it the issues went away and worked as expected.
Probably a edge case for many, but thought I'd mention if this saves some headache from someone in the future π
I got a hydration error after running pnpm start following a build.
import tailwind from "~/styles/tailwind.css?url";
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: tailwind }],
}),
component: RootComponent,
});
"tailwindcss": "4.0.0-beta.8",
"@tanstack/react-router": "^1.92.6",
"@tanstack/start": "^1.92.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vinxi": "^0.5.1"
I have same issue, is anyone can help? π«£
Anybody have any success with this?
I can't figure how to get pandacss to work with tanstack start. I tried importing the index.css in the __root.tsx file but that doesn't do anything. pandacss generates styled-system/ folder, but I don't know how to include in the build...
Has anyone used tanstack start and panda css together before?
working on this. there is still work left for making this smooth during dev.
I'm having the same issue with Vanilla Extract. It works fine with Tanstack Router only in a Vite/React app, but when I switch to Tanstack Start, no CSS is generated. I've tried the methods above. Thanks.
I just got tailwind v4 working with Start by:
-
running
npx @tailwindcss/upgrade@next -
going into the
app.config.tsand adding the@tailwindcss/viteplugin. -
importing
appCss from "@/styles/index.css?url"in my_root.tsx -
and setting it in the head:
...
head: () => ({
...
links: [
{
rel: "stylesheet",
href: appCss,
},
],
}),
I have hydration error too. Using ....css?url in head links. When run page with disabled JS I dont have styles
I was able to get pandacss working with tanstack start, but I haven't been able to get HMR working yet.
Crawling the panda and tanstack start/router docs over and over, I ended up with the following:
- setup basic tanstack project https://tanstack.com/start/latest/docs/framework/react/build-from-scratch
- setup panda https://panda-css.com/docs/installation/cli
- create
app.config.tsas:
import { defineConfig } from "@tanstack/start/config"
import tsConfigPaths from "vite-tsconfig-paths"
import pandacss from "@pandacss/dev/postcss"
export default defineConfig({
vite: {
// allow path aliases based on tsconfig
plugins: [tsConfigPaths({ projects: ["./tsconfig.json"] })],
css: { postcss: { plugins: [pandacss()] } },
}
})
- create
tsconfig.tsas:
{
compilerOptions: {
// ...
paths: {
"components/*": ["./app/components/*"],
"panda/*": ["./styled-system/*"],
}
},
include: ["app", "styled-system"]
}
- create
panda.config.tsas:
import { defineConfig } from "@pandacss/dev"
export default defineConfig({
// ...
include: ["./app/**/*.{js,jsx,ts,tsx}"],
// importMap still doesn't fix panda hmr in tanstack start despite pandacss suggestions:
// https://panda-css.com/docs/overview/faq#hmr-does-not-work-when-i-use-tsconfig-paths
importMap: "panda",
// required for styled-system/jsx folder to be generated
jsxFramework: "react"
})
- create
index.cssfile atapp/index.css - in
app/routes/__root.tsx, I import both css files:import styles from '../index.css?url'and using path aliasimport "panda/styles.css" - in the
createRootRoutefn, add styles aslinks: [{ rel: "stylesheet", href: styles }] - run
pnpm panda init -p --jsx-framework react --forceto make sure thestyled-system/jsxfolder is generated
At this point, I can import { css } from 'panda/css and apply styles like <div className={css({ color: "red", fontWeight: "bold", border: "1px solid red" })}>Hello</div> and it works. I can also create custom components:
// app/components/flex/index.tsx
import React from 'react'
import { motion, MotionConfig, Transition } from "framer-motion"
import { type HTMLStyledProps, styled } from "panda/jsx"
const Div = styled(motion.div)
export type DivProps = HTMLStyledProps<typeof Div> & { _motion?: Transition; }
export default function Flex(props: DivProps) {
const { _motion, ...rest } = props
return (
<MotionConfig transition={_motion}>
<Div display="flex" {...rest} />
</MotionConfig>
)
}
and use throughout application like:
return <Flex flexWrap="wrap" gap={8} color="red.500">[1, 2, 3]</Flex>
The single remaining issue with this setup is if I change color to "blue.500", the color doesn't change until I reload the page, but if I add 4 to the array, the UI updates. So its just the panda styles that don't have HMR.
UPDATE: I was able to get pandacss hmr to work by replacing the import "panda/styles.css" import with import "panda/styles.css?url".
However, after some time, this stops working as well. My guess is the websocket connection expires and there's no attempt to reconnect? So I often have to restart the server to get the HMR working again
Hi, any update on how to use CSS Module? The dev version works (with FOUC) but the build version doesn't work (the CSS file is not included in the HTML)
For now, I have to workaround by not enable CSS split, i.e., CSS is injected by JS during runtime. It doesn't provide the best UX when clients have a slow machine or disabled JS
Not sure if itβs the same issue, but I came across an issue with CSS Modules hashing (the hashes written into CSS files are different from those used in rendered HTML). I changed the CSS transformer to LightningCSS and problems solved.
/* app.config.ts */
export default defineConfig({
vite: {
css: {
transformer: 'lightningcss' // π
}
}
})
@imcvampire still work in progress. can you please share your setup (ideally a complete example) so I get a better understanding?
@schiller-manuel You can check this example: https://codesandbox.io/p/devbox/staging-frost-4r7mkc?workspaceId=ws_VkPYB3nPBCbgM5vW4nx67Q.
There are 2 changes:
- Component
WithCSSModulewhich is a component using CSS module -
app.config.jshas 2 plugins which is workaround to support CSS module on the production build
The CSS module code is extracted into separate files but they aren't added to the SSR HTML
yep and I am working on that. stay tuned.
just to let you know, this will take some more time. CSS module support will be tackled once we have migrated off vinxi, which is currently ongoing.
Ummmmm so this is why this is a beta o_o