ProNextJS
    Loading
    lesson

    Component Locations

    Jack HerringtonJack Herrington

    Deciding where to put components in your web applications can be more challenging than you might expect.

    In this lesson we'll discuss different ways to organize components, based on whether they are used within a single route, across an entire application, or shared between multiple applications.

    For this example, we'll look at a Turborepo monorepo with two separate applications.

    Turborepo Monorepo

    In this setup, we have App 1 on the left, which is the home page, and a sub-page of App 1 in the middle. On the right side, we have App 2.

    turborepo example

    The subpage in the center has a Heading component that's only used on that specific route.

    Route-Specific Components

    The Heading.tsx file is located inside of the apps/app-one/src/app/subpage directory.

    Since this component is only used by one route, it makes sense to keep it directly within the specific route directory itself.

    The Heading.tsx component file is exactly where it should be – placed within the subpage directory along with the page.tsx file that uses it. This clearly shows that the Heading component is tied to this route and not meant to be used elsewhere.

    If you have several components specific to this route, you can create a components subdirectory within the route directory to keep things tidy. In this case, apps/app-one/src/app/subpage/components.

    Application-Wide Components

    Now, imagine we want to use the Heading component on both the main page and the subpage of App 1. Keeping it within the subpage directory no longer makes sense. Instead, we should move it to a spot that shows it's available for use throughout the application.

    For this example, there's already an apps/app-one/src/components/ui directory containing shadcn components.

    The apps/app-one/components.json file specifies the configuration for shadcn, including a component aliases option.

    By default, the alias is set to @/components:

    // inside apps/app-one/components.json
    {
      ...
      "aliases": {
        "@components": "@/components"
      }
    }
    

    However, since we want to have our own components in the components directory, we'll create a new components/shadcn/ui directory and move the shadcn components there. Then we will update the aliases configuration to point to our new directory:

    // inside apps/app-one/components.json
    {
      ...
      "aliases": {
        "@components": "@components",
        "@shadcn": "@/components/shadcn"
      }
    }
    

    We can then import the shadcn components by pointing to them. For example, in page.tsx we can import the shadcn Button component like this:

    import { Button } from '@/components/shadcn/ui/button';
    

    With the shadcn components taken care of, we can move the Heading component to the src/app-one/app/components directory. This way, it's clear that the Heading component is intended to be used throughout the application.

    If you are using a design system with several different components, you could create a design-system directory within the components directory. This way, you can keep all shared components in one place.

    Sharing Components Across Applications

    Sometimes, you might want to share components between multiple applications within a monorepo.

    The first step is to create a new ui/src subdirectory inside of the apps/packages directory. Then, move the Heading component in so it is at apps/packages/ui/src/Heading.tsx.

    Note that this component was named with a capital letter, while others are lowercase. We'll rename it to match, but for your own projects this is a decision you'll need to make with your team early on.

    To expose the shared components directory, we need to update the apps/packages/ui/package.json file to define the exports:

    // inside apps/packages/ui/package.json
    {
      "name": "@repo/ui",
      ...
      "exports": {
        "./button": "./src/button.tsx",
        "./card": "./src/card.tsx",
        "./code": "./src/code.tsx",
        "./heading": "./src/heading.tsx"
      },
      ...
    

    With this setup, we can import the Heading component into any application within the monorepo using the name from the package.json file:

    // In App 1 or App 2 components
    import Heading from '@repo/ui/Heading';
    

    Now when making a change to the shared Heading component, both apps will be updated to reflect it:

    changes work in both apps

    The Big Takeaway

    Your own use cases will inform which organization approach is best for your project. The key is to make sure your components are organized in a way that makes sense for how they will be used.

    For route-specific components, keep them in the route directory. For application-specific components, keep them in src/components. And for shared components, keep them in a package (whether an NPM or monorepo package) that can be imported across applications.

    This will help you keep your codebase clean and maintainable as your project grows.

    Transcript