Master Frontend Architecture: Configuring ESLint, Prettier, Tailwind,
and TypeScript globally

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.

Article content

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:

Article content

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:

Article content

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:

Article content

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:

Article content

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:

Article content

Why Centralize the Tailwind Configuration?

Centralizing tailwind.config.ts offers significant benefits:

  1. Shared Styles: Define global styles, themes, and customizations that all packages in your monorepo can use. For example, you can standardize your color palette, typography, or spacing across multiple projects.
  2. Scalability: As your monorepo grows, you can reuse the same configuration for additional packages without duplicating code. This is particularly useful if you create a design system package for shared components.
  3. Consistency: Ensures all packages follow the same design guidelines, reducing inconsistencies and improving maintainability.

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:

Article content

Explanation

  1. Base Configuration Import: The baseConfig comes from the root tailwind.config.ts and contains all shared settings, such as custom colors, fonts, and plugins.
  2. Local content Property: The content property in each package’s tailwind.config.ts ensures that Tailwind scans only the files within the respective package.
  3. Extending the Configuration: You can add package-specific settings in the local configuration without affecting the centralized baseConfig. For example, you might define custom themes or plugins specific to a package.

Key Benefits of This Approach

  • Centralized Consistency: Shared settings like colors and typography remain consistent across all packages.
  • Package Flexibility: Individual packages can still add their own configurations (e.g., specific paths, plugins, or themes).
  • Scalability: As your monorepo grows, you can reuse the base configuration while keeping package-specific tweaks isolated.

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.

Article content

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.

Article content

Benefits of Centralizing TypeScript Configurations

  1. Consistency: Shared settings like strict mode and module resolution are applied across all packages.
  2. Maintainability: Changes to shared configurations are made in a single file (tsconfig.common.json), reducing duplication.
  3. Scalability: As your monorepo grows, new packages can quickly extend the shared configuration without duplicating settings.
  4. Customization: Each package can still override or add its own configurations as needed.

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:

Article content

Key Points:

  • dev: Uses nodemon to reload the server during development.
  • lint: Uses the global lint script defined at the root level. If you notice the lint script will run "npm run --prefix ../../ lint", meaning that will execute the global lint script that is placed in the root package.json.

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:

Article content

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:

Article content

Key Points:

  • extends: Ensures shared settings from the root-level tsconfig.common.json are applied.
  • outDir: Define the build output directory and the root directory for source files.

Step 4: Create the nodemon.json File

To simplify development with nodemon, create a nodemon.json file in the packages/backend directory:

Article content

Key Points:

  • watch: Watches the src directory for changes.
  • ext: Specifies the file extension to watch (ts).
  • exec: Runs the src/index.ts file using ts-node.

Step 5: Create the .env File

Add a .env file to store environment variables. For example:

Article content

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:

  • A design system package to share UI components.
  • Testing configurations with tools like Jest or Playwright.
  • Deployment pipelines for frontend and backend packages.

This setup ensures a maintainable and scalable monorepo, ready for modern development needs.

Code: https://github.com/BeehindTheCode/OpenStay/pull/1

To view or add a comment, sign in

Others also viewed

Explore topics