Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Setup Next.js with TailwindCSS

profile picture
Author
Fabian Huth
Published
2024-08-02
Time to read
10 minutes reading time

Introduction

This tutorial shows how to install Next.js in combination with TailwindCSS to create a basic setup for various frontend projects.

Next.js is a ReactJS frontend framework that provides a lot of useful features. It not only can serve as a static site generator, but also has advanced strategies to fetch data from different sources to display dynamic content.

TailwindCSS is a CSS framework that adopts a utility-first approach to apply CSS class names. This provides an efficient way to create modern layouts & designs without creating to much hassle around naming conventions.

Prerequisites

  • System with MacOS, Windows or Linux
  • Latest version of Node.js and npm installed

    You can install them with brew:

    brew install node@22
    node -v &&  npm -v

Step 1 - Install Next.js

You can install Next.js either automatically or manually.

  • Automatically:

    npx create-next-app my-project [--use-npm]
  • Manually:

    mkdir ~/my-project && cd ~/my-project
    npm install next react react-dom

    After you installed Next.js, edit the new file ~/my-project/package.json and add the section "scripts" as shown here:

    {
      "dependencies": {
        <your_dependencies>
      },
      "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
      }
    }

Step 2 - Install TailwindCSS

TailwindCSS uses PostCSS as a preprocessor and autoprefixer as additional dependencies. This is optional but recommended, because Tailwind offers some nice features when using these plugins, e.g. applying non-standard keywords like @apply, @theme or theme() that can then be used in our custom CSS file.

cd ~/my-project
npm install -D tailwindcss postcss autoprefixer

After that, we can create a config file for TailwindCSS:

npx tailwindcss init -p

This will create tailwind.config.js and postcss.config.js.

Step 3 - Configure Next.js

To define routes for our application we can just add additional JavaScript files to the app directory. In this tutorial we will focus on pages that are not dynamically generated. Because in a lot of cases it is also necessary to generate routes by dynamic data, e.g creating routes depending on an id. You can find an excellent guide on how to achieve this in the Next.js documentation (make sure the menu is set to "Using App Router").

Add Pages

To create pages, you need a subdirectory called app. Within app, you need two files:

File Description
app/layout.tsx This file contains information about the layout that is automacially applied to all pages including the main page and all subpages.
app/page.tsx This contains the content for the main page.

Additionally, you can add as many subdirectories as you want. Each subdirectory with a page.tsx file, represents a subpage for your application.

In this tutorial, we will use the following example:

app/
├── layout.tsx          # layout of main page and all subpages
├── page.tsx            # content main page
└── example-subpage/
    ├── layout.tsx      # additional layout requirements for example-subpage
    └── page.tsx        # content example-subpage

This example will give us two pages:

http://<ip-address>:3000                   # main page
http://<ip-address>:3000/example-subpage   # subpage

If you installed Next.js manually, create the directory for the application files:

mkdir ~/my-project/app

Now, you can add the content for your pages:

  • Main page

    nano ~/my-project/app/page.tsx

    Use this content for the main page:

    // ~/my-project/app/page.tsx
    
    export default function Page() {
      return <h1 className="text-white text-3xl font-bold pl-5">Welcome to Next.js and TailwindCSS</h1>
    }

  • Subpage

    Add a new subdirectory for the subpage and create page.tsx:

    mkdir ~/my-project/app/example-subpage
    nano ~/my-project/app/example-subpage/page.tsx

    Use this content for the subpage:

    // ~/my-project/app/example-subpage/page.tsx
    
    export default function Page () {
      return (
        <>
        <div className="flex flex-col place-content-center items-center col-span-1 pr-3">
          <div className="border-2 rounded-full p-0.5 border-gray-600 mb-2">
            <img
              className="rounded-full w-100 h-100"
              src="https://picsum.photos/100/100"
              alt="random image from unsplash"
            />
          </div>
        </div>
        <div className="col-span-5 pr-3">
          <h2 className="text-white text-3xl font-semibold pl-4 mb-3">Welcome to Next.js and TailwindCSS</h2>
          <span className="text-gray-500 text-lg font-bold block pl-4 mb-3">Bringing both frameworks together</span>
          <p className="text-white pl-4 leading-7">
            Cats are believed to be the only mammals who don't taste sweetness. Cats are nearsighted, but their peripheral
            vision and night vision are much better than that of humans. Cats are supposed to have 18 toes (five toes on
            each front paw; four toes on each back paw). Cats can jump up to six times their length.
          </p>
        </div>
        </>
     )
    }

Add the layout (TailwindCSS)

Now that we have the content for our pages, we can add the layout in which the pages should be displayed.

  • General layout for main page and all subpages

    As mentioned before, the layout defined in app/layout.js is automatically applied to the main page and all subpages.

    Choose one of those two alternatives to include TailwindCSS:

    • Alternative 1: Via Javascript

      This is a convenient way of adding TailwindCSS without having to write any other stylesheet.

      nano ~/my-project/app/layout.tsx

      Use this content:

      // ~/my-project/app/layout.tsx
      
      import 'tailwindcss/tailwind.css'
      
      export default function RootLayout({
        children,
      }: {
        children: React.ReactNode
      }) {
        return (
          <>
          <html lang="en">
            <head>
              <title>Next.js and TailwindCSS</title>
              <link rel="icon" href="/favicon.ico"/>
            </head>
            <body className="min-h-screen bg-gradient-to-tr from-midnight-100 via-midnight-200 to-midnight-300 flex flex-col justify-center">{children}</body>
          </html>
          </>
        )
      }

    • Alternative 2: Via CSS

      Another possibility is to add Tailwind's styles by CSS. To achieve this, we will import the global stylesheet in the app/layout.tsx file.

      Create app/layout.tsx:

      nano ~/my-project/app/layout.tsx

      Use this content:

      // ~/my-project/app/layout.tsx
      import './globals.css'
      
      export default function RootLayout({
        children,
      }: {
        children: React.ReactNode
      }) {
        return (
          <>
          <html lang="en">
            <head>
              <title>Next.js and TailwindCSS</title>
              <link rel="icon" href="/favicon.ico"/>
            </head>
            <body className="min-h-screen bg-gradient-to-tr from-midnight-100 via-midnight-200 to-midnight-300 flex flex-col justify-center">{children}</body>
          </html>
          </>
        )
      }

      Create the global stylesheet app/global.css:

      /* ~/my-project/app/globals.css */
      @tailwind base;
      @tailwind components;
      @tailwind utilities;

      Unfortunately Tailwind does not offer an in-depth explanation of what the @tailwind directive does in detail, other than injecting generated styles (based on the configuration of Tailwind) into the stylesheet at build-time.



  • Custom layout for subpages

    Each subpage has its own layout file. When you have additional layout requirements, you can either save them directly in the layout file of the subpage or save them in a dedicated directory and import this into the layout file of the subpage. Saving additional layout specifications in a dedicated directory makes it easier to reuse the layout on several different subpages. So this makes sense if you need the same layout for several different subpages.

    Create the directory for additional layouts:

    mkdir ~/my-project/components

    Create the additional layout:

    nano ~/my-project/components/FancyCard.js

    Add this content:

    // ~/my-project/components/FancyCard.js
    'use client'
    
    import React from 'react'
    
    const FancyCard = ({ children }) => {
      return (
        <div className="max-w-xl mx-auto">
          <div className="p-8 bg-midnight-200 shadow-xl rounded-3xl border-4 border-gray-600">
            <div className="grid grid-cols-6 gap-0 divide-x divide-gray-600">
              {children}
            </div>
          </div>
        </div>
      )
    }
    
    export default FancyCard

    Now create the layout.tsx file for your subpage and apply the additional layout specifications:

    nano ~/my-project/app/example-subpage/layout.tsx
    // ~/my-project/app/example-subpage/layout.tsx
    
    import FancyCard from '../../components/FancyCard'
    
    export default function Layout({ children }) {
      return <FancyCard>{children}</FancyCard>
    }

    As mentioned before, you can add more layouts in components as needed and import them as shown above in the layout.tsx file of any subpage that should use that particular layout. If you plan to use a layout only once, you can add it directly in the layout.tsx file of that subpage.

Step 4 - Configure TailwindCSS

The final step is to customize the configuration files for Tailwind and PostCSS.

By running npx tailwindcss init -p we have already created two configuration files:

  • tailwind.config.js
  • postcss.config.js

If we want to use PostCSS as preprocessor, we can use the postcss.config.js for additional functions, like adding vendor prefixes, adding global CSS reset or generating @font-face rules.

Another great feature in TailwindCSS is that you can easily customize the default theme. By changing the theme property in the tailwind.config.js, we can customize, e.g. custom breakpoints, colors, fonts or more detailed characteristics, like spacings, border radiuses or box shadows. By changing the colors key, it is possible to add our own custom color palette to the existing theme:

nano ~/my-project/tailwind.config.js

Add the following content:

// ~/my-project/tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    colors: {
      ...defaultTheme.colors,
      'white': '#ffffff',
      'black': '#242424',
      'midnight': {
        '100': '#03060a',
        '200': '#001a40',
        '300': '#060b17',
        // add more color variants if you want to
        },
      'gray': {
        '500': '#6b7280',
        '600': '#4b5563',
        },
      'purple': {
        '500': '#6d28d9',
        },
      // add more colors if you want to
    },
  },
  plugins: [],
}

Additionally, we're spreading the default color schema, so we're still able to use Tailwind's default color palette. Note that we can now apply our colors on multiple utility classes by using the defined color and the corresponding variant, bg-{color}-{variant}. E.g.:

<div className="bg-midnight-300">
  ...
</div>

Step 5 - Start the application

Now that everything is set, we can start the application to view our result:

  • To access via private IP (localhost):

    npm run dev
  • To access via public IP:

    npm run dev -- -H 0.0.0.0

Open both pages in a webbrowser:

http://<ip-address>:3000
http://<ip-address>:3000/example-subpage

This should look like the following:

Final result

Conclusion

We successfully set up a Next.js application that uses Tailwind as a CSS framework.

The usage of utility-first CSS frameworks creates a lot of variance in opinion and as usual there is no universal verdict. Some say that it's messy and hard to read, that there's no difference to inline-styles or that it violates separation of concerns. I personally think that all these points can be contradicted and recommend the following two articles to get an impression from both sides. This way you get a feeling if it is the right framework for your next project.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2025 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more