Last weekend I came across an issue with React Router V7 and Styled Components. It was fairly easy to install and get them working together, until I realized that styled components classes don’t include display names by default.

Unfortunately, the usual way of making styled components prefix classes with the names of the files they’re defined in doesn’t work with React Router v7. In this post, I’ll show you a workaround for this issue.
Why styled components and not Tailwind CSS?
I switched to styled components from Tailwind CSS for personal projects because I simply got fed up with it.
After a year of using Tailwind, I believe it:
- Makes code difficult to read (media queries…)
- Follows an inconsistent syntax (
border-1
vsborder-2
,align-*
vsalign-items
vsalign-content
in CSS) - Bloats the DOM
- Makes it tricky to retain consistent styles across components
I like to keep things simple. CSS is very powerful, and since I know it pretty well, I’m not afraid to use it. Check the links at the end of this post to learn more about why Tailwind might not be for you.
Going back to styled components with React Router V7
I need named classes so I can quickly look up the source files. This is a significant improvement to my developer experience over Tailwind’s inline utility classes, which—when conditional—are hard to track down.
The usual way to install styled components in React Vite-based projects would be to configure it by passing Babel options to the react()
plugin in vite.config.ts
:
import { reactRouter } from '@react-router/dev/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
server: {
port: 3010,
},
plugins: [
tsconfigPaths(),
react({
babel: {
babelrc: false,
configFile: false,
presets: [['@babel/preset-typescript', { isTSX: true, allExtensions: true }]],
plugins: [
[
'babel-plugin-styled-components',
{
displayName: true,
fileName: true,
ssr: true,
pure: true,
meaninglessFileNames: ['index', 'styles'],
},
],
],
},
}),
reactRouter(),
],
publicDir: 'public',
ssr: {
noExternal: ['styled-components'],
},
esbuild: {
logOverride: { 'this-is-undefined-in-esm': 'silent' },
},
});
The problem with this setup becomes apparent as soon as you load the page. You’ll get the following error in your browser console:
❌ Uncaught SyntaxError: Identifier 'RefreshRuntime' has already been declared (at root.tsx:3:1)
The problem stems from the fact that you’re using both react()
followed by reactRouter()
. React Router v7’s architectural decision to bundle React transformation capabilities directly into its plugin created incompatibilities with traditional React Vite plugins. The @react-router/dev/vite
plugin includes its own React Fast Refresh implementation, JSX transformation, and HMR logic optimized for route-based code splitting. Here’s a GitHub issue talking about that. The issue got closed.
Unfortunately, the reactRouter()
plugin doesn’t support the same parameters as the traditional react()
plugin, and there’s no clear way of making it work with styled components.
After some research, I finally stumbled upon this long discussion on GitHub and came up with the following solution based on the comment by AlemTuzlak.
Here’s the updated vite.config.ts
file:
import { reactRouter } from '@react-router/dev/vite';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import babel from 'vite-plugin-babel';
export default defineConfig({
server: {
port: 3010,
},
plugins: [
tsconfigPaths(),
reactRouter(),
babel({
filter: /\.(t|j)sx?$/,
exclude: [/node_modules/], // Critical: exclude node_modules
babelConfig: {
babelrc: false,
configFile: false,
presets: [['@babel/preset-typescript', { isTSX: true, allExtensions: true }]],
plugins: [
[
'babel-plugin-styled-components',
{
displayName: true,
fileName: true,
ssr: true,
pure: true,
meaninglessFileNames: ['index', 'styles'],
},
],
],
},
}),
],
publicDir: 'public',
ssr: {
noExternal: ['styled-components'],
},
esbuild: {
logOverride: { 'this-is-undefined-in-esm': 'silent' },
},
});
Remember to install the dependencies:
npm install --save-dev vite-plugin-babel @babel/preset-typescript
And there you go! Your styled components should now result in non-cryptic names, giving you an easy way to look them up in your codebase:
components__Form-sc-1vm6dna-2 gVbqBe
Happy coding!