Master Frontend Architecture: Configuring ESLint, Prettier, Tailwind, and TypeScript globally
In my previous article, "Implementing Next.js 15 in a Monorepo," I walked you through the process of setting up Next.js 15 in a monorepo environment. While the focus of that article was primarily on installing and configuring Next.js for the frontend package, we also implemented ESLint, Prettier, Tailwind and TypeScript as part of our development workflow. However, these tools were configured specifically for the frontend package.
Since we're working with a monorepo setup, it's more efficient and maintainable to centralize shared tools like ESLint, Prettier, Tailwind and TypeScript at the root level. This approach allows all packages in the monorepo to use the same configuration, ensuring consistency across the entire codebase.
In this article, we'll take the next step in building our monorepo by moving our ESLint, Prettier, Tailwind and TypeScript configuration to the root of the project. We’ll also create a new backend package that will share the same linting and formatting tools, demonstrating how to standardize development practices across both frontend and backend packages in a monorepo.
Implementing ESLint at Root Level
Step 1: Remove Obsolete Files
Now that we are using ESLint 9 there are some obsolete files that we need to remove, first the .eslintignore and .eslintrc.json.
Step 2: Update .gitignore at the Root Level
Since we've moved the .gitignore file to the root directory, we need to ensure it includes entries to ignore specific directories inside each package. These typically include build directories like node_modules and dist.
Make sure your .gitignore looks something like this:
Step 3: Create eslint.config.mjs at the Root Level
Next, create a new file named eslint.config.mjs in the root directory. This will be the centralized ESLint configuration file for the entire monorepo.
Here’s an example of what your eslint.config.mjs file should look like:
Why Conditionally Add next/core-web-vitals?
The next/core-web-vitals configuration requires a pages directory, which doesn’t exist in other packages like the backend. By adding it conditionally only when the NEXT_LINTER environment variable is set, we avoid warnings when linting non-frontend packages.
Step 4: Update Frontend package.json
In the frontend package, update the scripts section in packages/frontend/package.json to include the NEXT_LINTER variable for linting:
This ensures that next/core-web-vitals is included only when linting the frontend package.
Step 5: Update the Root package.json
Finally, update the scripts section of the root-level package.json to lint all packages in the monorepo. You may also want to install the ts-node package for improved TypeScript support:
The lint script here targets all src directories under packages, ensuring consistent linting across the monorepo.
Implementing Tailwind CSS at Root level
To streamline your monorepo setup, you can centralize the Tailwind CSS configuration by moving tailwind.config.ts to the root directory. This approach ensures a single source of truth for your Tailwind configuration, enabling shared styles and utilities across all packages.
Step 1: Move tailwind.config.ts to the Root
Relocate the existing tailwind.config.ts file from the frontend package to the root directory. The file should look like this:
Why Centralize the Tailwind Configuration?
Centralizing tailwind.config.ts offers significant benefits:
Step 2: Each Package Must Have Its Own tailwind.config.ts
When centralizing the Tailwind configuration in a monorepo, you still need to create a tailwind.config.ts file in each package that uses Tailwind. Without this, Tailwind won’t function properly in those packages. The solution is to have a local tailwind.config.ts in each package, which imports and extends the centralized configuration.
Creating a Local tailwind.config.ts
For example, in the frontend package, create a new tailwind.config.ts file that imports the root-level configuration and extends it as needed:
Explanation
Key Benefits of This Approach
Implementing TypeScript at Root level
To streamline TypeScript configurations across a monorepo, it's efficient to centralize shared settings in a common configuration file at the root. Packages can then extend this base configuration, reducing redundancy and ensuring consistency.
Step 1: Create tsconfig.common.json at Root Level
At the root of your monorepo, create a file named tsconfig.common.json. This file will define shared TypeScript settings that all packages can inherit.
Step 2: Update tsconfig.json in the Frontend Package
Each package in your monorepo should have its own tsconfig.json that extends the root-level tsconfig.common.json. This allows packages to include their own specific configurations or overrides.
Benefits of Centralizing TypeScript Configurations
Implementing Backend Package
To add a backend package to your monorepo, follow these steps. This process ensures consistency with shared configurations and sets up the backend for smooth development and integration with the rest of your monorepo.
Step 1: Create the package.json
In the packages/backend directory, create a package.json file to define the backend package. It should look something like this:
Key Points:
Step 2: Create the src/index.ts File
Add an entry point for your backend application at packages/backend/src/index.ts. Here's a basic Express server setup:
Step 3: Create the tsconfig.json File
In the packages/backend directory, create a tsconfig.json file to define TypeScript settings for the backend package. Extend the shared configuration for consistency:
Key Points:
Step 4: Create the nodemon.json File
To simplify development with nodemon, create a nodemon.json file in the packages/backend directory:
Key Points:
Step 5: Create the .env File
Add a .env file to store environment variables. For example:
Conclusion
By centralizing configurations and leveraging the flexibility of a monorepo, we’ve built a foundation that promotes consistency, scalability, and maintainability across both frontend and backend packages.
With ESLint, Tailwind CSS, and TypeScript set up at the root level, we’ve streamlined our development process and reduced redundancy, enabling shared tools and configurations to work seamlessly across packages. Each package—whether it’s the backend or frontend—can extend these shared settings while keeping the flexibility to add its own customizations.
The backend package, complete with a lightweight Express server, TypeScript integration, and nodemon for development efficiency, demonstrates how new services can easily fit into this monorepo architecture.
This approach not only ensures a unified developer experience but also simplifies onboarding for new team members, accelerates iteration, and prepares the project for future growth. Whether you’re adding new features, expanding with more services, or introducing a design system, this monorepo setup is robust enough to adapt to evolving requirements.
By investing in this structured setup early, you’re setting the stage for a more efficient, scalable, and collaborative development environment. Let me know if you’d like to explore further improvements, such as adding CI/CD pipelines, testing strategies, or advanced monorepo workflows!
Next Steps
With this foundation, you can expand your monorepo to include more features, such as:
This setup ensures a maintainable and scalable monorepo, ready for modern development needs.