How I structure my Next.js projects when using TailwindCSS

October 16, 2021
views 3 min read

Let's get one thing straight, I love TailwindCSS, I'm using it for almost all my projects nowadays. But, it can be hard maintaining a project using Tailwind, mostly because it's really easy to fall off the tracks by not applying the DRY principle.

In this article I go through how I usually structure my projects when using Tailwind.

This article is assuming you're using Next.js with Typescript + TailwindCSS. If you don't know how to approach that, take a look at this article.


Simplify resolvers

One of the first things I do when starting a project is making sure I simplify the way I import components, containers, styles, etc into my project, to do that run:

npm i -D babel-plugin-module-resolver

Once it's done, open tsconfig.json and inside the compilerOptions add:

"paths": {
  "@/*": ["./*"],
}

Great, just one step missing, create a .babelrc file in the root of your project and put this inside it:

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "module-resolver",
      {
        "root": ["./"],
        "alias": {
          "@": "."
        }
      }
    ]
  ]
}

What's the purpose of this?

// Importing now
import Avatar from '@/components/Avatar'

// Importing before
import Avatar from '../../../components/Avatar'

TailwindCSS

How does a button in Tailwind looks like? Here's an example:

<button className="px-5 py-2.5 rounded-lg bg-indigo-500 text-white hover:bg-indigo-800">
  Add to cart
</button>

Imagine re-using this all over your application, repeating all of this, uff.

Reusable components to the rescue, here's an example of a Button component:

const Button: React.FC = ({ children }) => {
  return (
    <button className="px-5 py-2.5 rounded-lg bg-indigo-500 text-white hover:bg-indigo-800">
      {children}
    </button>
  )
}

export default Button

Now, you can simply use <Button>Add to cart</Button> all over your application and the buttons will look exactly the same.


File Structure

I like to have at least 2 folders, components and containers.

Components

Here lives all components, such as Button, Typography, Table, Tag, etc;



Containers

Here lives stuff such as Layout, PrivateRoute, PublicRoute, etc;



Since in components only live small components, I also like to create an index.ts file to import all components into it, why? Take a look at this 2 examples:

// Without index.ts
import Button '@/components/Button'
import Typography '@/components/Typography'
import Table '@/components/Table'
import Tag '@/components/Tag'

// With index.ts
import { Button, Typography, Table, Tag } from '@/components'

We went from 4 lines to just 1.

Here's a full file structure with the examples we just talked about:

components/
  Button/
    Button.tsx
    index.ts
  Typography/
    Typography.tsx
    index.ts
  Table/
    Table.tsx
    index.ts
  Tag/
    Tag.tsx
    index.ts
containers/
  Layout/
    Layout.tsx
    index.ts
  PrivateRoute/
    PrivateRoute.tsx
    index.ts
  PublicRoute/
    PublicRoute.tsx
    index.ts

All of these will reusable across your application, if you need to add a new property or change a little the design in one of those components, it'll change everywhere.


Storybook

Using Storybook is also a great improvement to your application, leaving only the detached components in the components folder will make it easier to create Storybook stories, you just need to add a new file per component:

components/
  Button/
    Button.stories.tsx
    Button.tsx
    index.ts

If you want to go even further go ahead and learn more about Atomic Design by Brad Frost.


Hope this article brought some value to you.

Read next
Tailwind with TypeScript not working on Vercel
February 5, 2021
views