DEV Community

Cover image for Setup Next.js to use Tailwind with Nx
Juri Strumpflohner for Nx

Posted on • Originally published at blog.nrwl.io

Setup Next.js to use Tailwind with Nx

In the previous article we learned how to setup Next.js in an Nx workspace. In this article we carry that forward, by adding TailwindCSS support to our setup.

It's important to note that you don't need to use a preprocessor with Tailwind — you typically write very little CSS on a Tailwind project anyways so using a preprocessor just isn't as beneficial as it would be in a project where you write a lot of custom CSS.

The official TailwindCSS docs page already has a guide on how to setup Tailwind with Next.js. Definitely check that out.

Install and configure Tailwind in an Nx workspace

The first step is to install the necessary npm packages.

yarn add tailwindcss@latest postcss@latest autoprefixer@latest
Enter fullscreen mode Exit fullscreen mode

The next step is to create the tailwind.config.js as well as postcss.config.js files. Tailwind already comes with a utility for that. Note, previously we generated our app (named site) into the apps folder of Nx. Therefore, when generating the Tailwind configuration, we need to cd into that folder.

cd apps/site
npx tailwindcss init -p 
Enter fullscreen mode Exit fullscreen mode

That should generate both of the configuration files directly into the root of our Next.js application.

Make sure you adjust our postcss.config.js to properly point to our tailwind config file.

// apps/site/postcss.config.js
const { join } = require('path');

module.exports = {
  plugins: {
    tailwindcss: {
      config: join(__dirname, 'tailwind.config.js'),
    },
    autoprefixer: {},
  },
};
Enter fullscreen mode Exit fullscreen mode

Include the TailwindCSS styles

There are two options for including the Tailwind CSS files:

  1. Directly import them in the global _app.tsx component
  2. Include it in the styles.css css file that gets imported by the _app.tsx file

Option 1:

Open the main Next.js page component _app.tsx which functions and import the TailwindCSS file instead of styles.css.

...
import 'tailwindcss/tailwind.css';

function CustomApp({ Component, pageProps }: AppProps) {
  ...
}

export default CustomApp;
Enter fullscreen mode Exit fullscreen mode

Option 2:

Open styles.css, clean all the pre-generated CSS and include the TailwindCSS components there:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

styles.css gets imported by the _app.tsx and thus functions as the global CSS file for our Next.js app.

Finally also in the _app.tsx, remove the header section as we won't need it right now:

import { AppProps } from 'next/app';
import Head from 'next/head';
import './styles.css';

function CustomApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>Welcome to site!</title>
      </Head>
      <div className="app">
        <main>
          <Component {...pageProps} />
        </main>
      </div>
    </>
  );
}

export default CustomApp;
Enter fullscreen mode Exit fullscreen mode

Testing the integration

Let's quickly test whether the TailwindCSS integration works by adding the following to our index.tsx page component.

// apps/site/pages/index.tsx
export function Index() {
  return (
    <div className="bg-gray-50">
      <div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8 lg:flex lg:items-center lg:justify-between">
        <h2 className="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
          <span className="block">Ready to dive in?</span>
          <span className="block text-indigo-600">
            Start your free trial today.
          </span>
        </h2>
        <div className="mt-8 flex lg:mt-0 lg:flex-shrink-0">
          <div className="inline-flex rounded-md shadow">
            <a
              href="#"
              className="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700"
            >
              Get started
            </a>
          </div>
          <div className="ml-3 inline-flex rounded-md shadow">
            <a
              href="#"
              className="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50"
            >
              Learn more
            </a>
          </div>
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The result should look like this

How do we handle Tailwind config files in a monorepo

So far we've placed the Tailwind config within our app root directory (apps/site). That makes sense as the app probably knows the Tailwind configs to be designed properly. However, you might also want some more global, cross-app configs. Think about a company design system, where you'll most probably have the same font, maybe even colors etc.

To have a global Nx workspace-wide config we can leverage Tailwind presets. At the Nx workspace root we define a tailwind-workspace-preset.js.

Let's add the Tailwind Typography package:

yarn add @tailwindcss/typography
Enter fullscreen mode Exit fullscreen mode

Next, we add it to our tailwind-workspace-preset.js

// tailwind-workspace-preset.js
module.exports = {
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [require('@tailwindcss/typography')],
};
Enter fullscreen mode Exit fullscreen mode

In order to use the Tailwind preset in our apps/site specific Tailwind config, we require the file and add it to the presets array of the config.

// apps/site/tailwind.config.js

module.exports = {
  presets: [require('../../tailwind-workspace-preset.js')],
  ...
};
Enter fullscreen mode Exit fullscreen mode

Note, I've seen people use something like const { appRootPath } = require('@nrwl/workspace/src/utils/app-root'); and then concatenate it with the actual config file, which obviously also works and gets rid of the relative file import. Importing from @nrwl/workspace should be avoided though, and also it is a deep import of a private API that is subject to change.

Using a relative path should be fine here, as the app location will only rarely change.

Verify that your changes work by adding some paragraphs to your index.tsx. You can use those mentioned in the Tailwind Typography repo.

Tailwind CSS Purging

One of the main advantages of Tailwind is its CSS purging mechanism that allows reducing the final CSS bundle to only the required parts. As a result, you get a small and optimized CSS file. This happens at compile time using the PostCSS configuration.

Right now, if we run npx nx run site:export, we'll see that there's quite a large CSS file that gets exported:

This is because Tailwind during development pulls in all kinds of utility class names which you might never actually need.

Configure CSS Purging

To enable purging, open the tailwind.config.js for the site app and add the globs to the purge property. For our application site it might look as follows:

// apps/site/tailwind.config.js
const { join } = require('path');

module.exports = {
  purge: [
    join(__dirname, 'pages/**/*.{js,ts,jsx,tsx}')
  ],
  ...
};
Enter fullscreen mode Exit fullscreen mode

One of the particularities and also advantages of Nx is that its setup incentivizes the structuring of your logic into apps and libs (more details on the docs). As a result, though, our components having Tailwind classes might not only be in the apps/site/** but also within libraries in the libs/** folder. Naively we could just add those globs as well, like ./libs/**/*.{js,ts,jsx,tsx}. In a large workspace, this might however lead to unnecessary parsing of files since not all libs might be used by our Next app.

To solve this, we can dynamically calculate the glob pattern based on which libs our Next.js app depends on. Nx has a dependency graph, which can not only be used to visualize, but we can also leverage it in our custom scripts. Luckily we don't have to create our own script since in Nx v12.4.0 we added some utility functions that allow us to easily generate glob patterns based on the apps' dependencies.

Change your tailwind.config.js to the following:

// apps/site/tailwind.config.js
const { join } = require('path');

// available since Nx v 12.5
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');

module.exports = {
  presets: [require('../../tailwind-workspace-preset.js')],
  purge: [
    join(__dirname, 'pages/**/*.{js,ts,jsx,tsx}'),
    ...createGlobPatternsForDependencies(__dirname),
  ],
  darkMode: 'media', // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

Note how we import the createGlobPatternsForDependencies function from @nrwl/next/tailwind and pass it the current directory. What the function does is simply create a list of paths of potential libs our app depends on. For example, consider we depend on a lib ui and core the resulting generated paths would look like

/Users/yourname/juridev/libs/ui/src/**/!(*.stories|*.spec).tsx
/Users/yourname/juridev/libs/core/src/**/!(*.stories|*.spec).tsx
Enter fullscreen mode Exit fullscreen mode

(The exact path obviously depends on your username, Nx workspace name and operating system)

The glob pattern can be customized by specifying the 2nd parameter of the createGlobPatternsForDependencies function.

Finally, to verify the purging works as intended, execute the build or export target on our app (nx run site:build). The resulting CSS file should be only a couple of KBs.

Enabling Tailwind JIT

Since purging only happens at build time when you create the deployable bundle, the amount of CSS to load during development might be huge, depending on which TailwindCSS classes you apply to your HTML elements. This might result in an overall slowdown of the debugging tools (e.g. Chrome Devtools).

To mitigate that, the Tailwind team introduced the JIT mode, which (at the time of writing this article) is an experimental feature that can be turned on by adding the mode: 'jit' to the Tailwind config

// apps/site/tailwind.config.js

module.exports = {
  mode: 'jit',
  ...
};
Enter fullscreen mode Exit fullscreen mode

Tailwind's JIT compiler uses its own file watching system, so make sure to set the required environment variables like

  • TAILWIND_MODE to watch to get continuous watching as you have the dev server running and change the Tailwind classes. Set it to build for a one-off compilation
  • NODE_ENV should be set to production when doing the final build, s.t. the JIT compiler doesn't watch files, but just does a one-off compilation.

Note that Nx already sets the NODE_ENV to production when you build or export your Next.js app (e.g. using nx serve site ).

You can read all the details about JIT mode on the Tailwind docs.

Conclusion

In this article we learned about

  • how to install TailwindCSS into an Nx workspace
  • how to configure PostCSS and Tailwind
  • how to organize Tailwind config files in a monorepo
  • how setup CSS purging with Tailwind and how Nx helps to generate globs for dependent projects

GitHub repository

All the sources for this article can be found in this GitHub repository's branch: https://github.com/juristr/blog-series-nextjs-nx/tree/02-setup-tailwind

Discussion (0)