router icon indicating copy to clipboard operation
router copied to clipboard

How to use CSS Modules with TanStack Start?

Open calmqwe opened this issue 1 year ago β€’ 66 comments

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

calmqwe avatar Dec 16 '24 20:12 calmqwe

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'

roboncode avatar Dec 21 '24 05:12 roboncode

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

gioruu avatar Dec 21 '24 10:12 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",

gioruu avatar Dec 21 '24 10:12 gioruu

Thx @gioruu. I verified this works as well.

roboncode avatar Dec 23 '24 03:12 roboncode

@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

ghost avatar Dec 23 '24 16:12 ghost

@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.

nikolayemrikh avatar Dec 23 '24 18:12 nikolayemrikh

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

nikolayemrikh avatar Dec 23 '24 18:12 nikolayemrikh

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

ghost avatar Dec 23 '24 18:12 ghost

@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,
})

roboncode avatar Dec 24 '24 03:12 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, })

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

IRlyDontKnow avatar Dec 24 '24 12:12 IRlyDontKnow

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

TheOrdinaryWow avatar Dec 26 '24 08:12 TheOrdinaryWow

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 πŸ‘

joakimgrr avatar Dec 26 '24 10:12 joakimgrr

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"

richhost avatar Dec 28 '24 06:12 richhost

I have same issue, is anyone can help? 🫣

ra8ga avatar Jan 03 '25 07:01 ra8ga

Anybody have any success with this?

ghost avatar Jan 27 '25 08:01 ghost

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?

55Cancri avatar Feb 05 '25 02:02 55Cancri

working on this. there is still work left for making this smooth during dev.

schiller-manuel avatar Feb 05 '25 19:02 schiller-manuel

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.

spryzone avatar Feb 05 '25 20:02 spryzone

I just got tailwind v4 working with Start by:

  • running npx @tailwindcss/upgrade@next

  • going into the app.config.ts and adding the @tailwindcss/vite plugin.

  • importing appCss from "@/styles/index.css?url" in my _root.tsx

  • and setting it in the head:

...
 head: () => ({
...
    links: [
      {
        rel: "stylesheet",
        href: appCss,
      },
    ],
  }),

noah-wardlow avatar Feb 05 '25 23:02 noah-wardlow

I have hydration error too. Using ....css?url in head links. When run page with disabled JS I dont have styles

rlesniak avatar Feb 09 '25 07:02 rlesniak

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.ts as:
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.ts as:
{
  compilerOptions: {
    // ...
    paths: {
      "components/*": ["./app/components/*"],
      "panda/*": ["./styled-system/*"],
    }
  },
  include: ["app", "styled-system"]
}
  • create panda.config.ts as:
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.css file at app/index.css
  • in app/routes/__root.tsx, I import both css files: import styles from '../index.css?url' and using path alias import "panda/styles.css"
  • in the createRootRoute fn, add styles as links: [{ rel: "stylesheet", href: styles }]
  • run pnpm panda init -p --jsx-framework react --force to make sure the styled-system/jsx folder 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.

55Cancri avatar Feb 09 '25 12:02 55Cancri

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

55Cancri avatar Feb 09 '25 13:02 55Cancri

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

imcvampire avatar Feb 18 '25 09:02 imcvampire

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' // πŸ‘ˆ
    }
  }
})

SilentDepth avatar Feb 18 '25 10:02 SilentDepth

@imcvampire still work in progress. can you please share your setup (ideally a complete example) so I get a better understanding?

schiller-manuel avatar Feb 18 '25 18:02 schiller-manuel

@schiller-manuel You can check this example: https://codesandbox.io/p/devbox/staging-frost-4r7mkc?workspaceId=ws_VkPYB3nPBCbgM5vW4nx67Q.

There are 2 changes:

  • Component WithCSSModule which is a component using CSS module
  • app.config.js has 2 plugins which is workaround to support CSS module on the production build

imcvampire avatar Feb 19 '25 00:02 imcvampire

The CSS module code is extracted into separate files but they aren't added to the SSR HTML

Image

imcvampire avatar Feb 20 '25 12:02 imcvampire

yep and I am working on that. stay tuned.

schiller-manuel avatar Feb 20 '25 17:02 schiller-manuel

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.

schiller-manuel avatar Feb 27 '25 21:02 schiller-manuel

Ummmmm so this is why this is a beta o_o

daveycodez avatar Mar 18 '25 23:03 daveycodez