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
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
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: {},
},
};
Include the TailwindCSS styles
There are two options for including the Tailwind CSS files:
- Directly import them in the global
_app.tsx
component - 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;
Option 2:
Open styles.css
, clean all the pre-generated CSS and include the TailwindCSS components there:
@tailwind base;
@tailwind components;
@tailwind utilities;
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;
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>
);
}
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
Next, we add it to our tailwind-workspace-preset.js
// tailwind-workspace-preset.js
module.exports = {
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
};
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')],
...
};
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}')
],
...
};
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: [],
};
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
(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',
...
};
Tailwind's JIT compiler uses its own file watching system, so make sure to set the required environment variables like
-
TAILWIND_MODE
towatch
to get continuous watching as you have the dev server running and change the Tailwind classes. Set it tobuild
for a one-off compilation -
NODE_ENV
should be set toproduction
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
Learn more
๐ง Nx Docs
๐ฉโ๐ป Nx GitHub
๐ฌ Nrwl Community Slack
๐น Nrwl Youtube Channel
๐ฅ Free Egghead course
๐ง Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us ๐
Also, if you liked this, click the โค๏ธ and make sure to follow Juri and Nx on Twitter for more!
Top comments (0)