Tailwind CSS installation with Next.js and Styled Components

Outdated: This is an old Tailwind v1-era setup using
twin.macro, Styled Components, and custom Babel config. I am keeping it as an archive. For a current setup, use the official Tailwind CSS Next.js guide.
At the time, I wanted Tailwind utilities inside a Next.js app that already used
Styled Components. twin.macro was the bridge: it let me write Tailwind classes
inside CSS-in-JS without giving up the component style I was using.
I would not choose this shape for a new app. I am leaving the post here because old frontend notes are useful when they are honest about being old.
Create the app
yarn create next-app example
cd exampleInstall Tailwind, Styled Components, and the macro packages:
yarn add tailwindcss postcss autoprefixer twin.macro styled-components
yarn add -D babel-plugin-macros babel-plugin-styled-componentsConfigure the macro
Create babel-plugin-macros.config.js:
module.exports = {
twin: {
preset: 'styled-components',
},
}Then add a .babelrc so Next uses the macro and Styled Components plugin:
{
"presets": ["next/babel"],
"plugins": [
"babel-plugin-macros",
[
"styled-components",
{
"pure": true,
"ssr": true
}
]
]
}Add Tailwind globals
In _app.js, wrap the app with GlobalStyles from twin.macro:
import { GlobalStyles } from 'twin.macro'
const App = ({ Component, pageProps }) => (
<>
<GlobalStyles />
<Component {...pageProps} />
</>
)
export default AppInitialize Tailwind:
npx tailwindcss initBack then I also needed a webpack workaround for the fs module:
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.node = { fs: 'empty' }
}
return config
},
}That line dates the article more than anything else. If a styling setup needs a custom Babel config and a webpack shim before the first component renders, I now treat that as a warning sign.
Try a small component
import tw, { css } from 'twin.macro'
export default function Index() {
return (
<div
css={[
css`
height: 100vh;
`,
tw`flex items-center justify-center bg-black text-2xl text-white`,
]}
>
Hello, World!
</div>
)
}This worked. It also made upgrades annoying. Every Next release meant checking the macro, the Babel plugin, Styled Components SSR behavior, and Tailwind output at the same time. The component looked simple; the toolchain behind it was not.
Why this aged poorly
The setup depended on three moving parts that all had to agree with each other: Next's Babel pipeline, Styled Components SSR behavior, and Tailwind's generated utility classes. When it worked, it felt flexible. When it broke, the failure was usually in the glue rather than in the component.
For a small app, that trade is not worth it. A styling setup should not make every framework upgrade feel like a compatibility exercise. Today I would only use a macro bridge if the project already had a large Styled Components codebase and needed a gradual migration path.
For a new project, I would keep it simpler:
- use the official Tailwind integration,
- keep global tokens in one stylesheet,
- use component classes directly,
- add a design-system wrapper only after real repetition appears.
The current Tailwind path is less clever. I prefer it for exactly that reason.