Tailwind CSS installation with Next.js and Styled Components

Tailwind CSS installation with Next.js and Styled Components cover image

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 example

Install 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-components

Configure 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 App

Initialize Tailwind:

npx tailwindcss init

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