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

    Let's talk about where you're going to put your components inside of your applications. It might not be as easy as you think it is. To start off with, we're going to start off with a Turbo Repo, Mono Repo. So we're going to have two different applications. App 1 on the far left-hand side, that's the homepage, because App 1.

    Then you've got a subpage of App 1 in the middle section, and then you've got app two on the other side. Now, right now we have a heading component that we have only in the sub page. So let's go take a look at where that's located. So over in our applications, we've got app one, And then inside of app one, we've got our app directory. And then inside of our app directory, you have subpage.

    And we've got heading right there. And I think that's actually the perfect spot for it. So if you have a component that's only used by one route, then I believe that component should be in that route directory itself. You could, if you wanted to, if you had a lot of components, create a new component folder there. That would be components just for that route, and then put that components in there.

    No problem. That's great. But Now let's go and say that we want to use heading in both the sub page and also the main page of app one. So we're gonna put that. Well we could take that component directory and pull it up into source but there's already a components directory there So let's take a look at what's going on with that.

    Well, in that components directory, we have UI as a subfolder and then button. Where does that come from? Well, that's coming from ShadCN. So if you're using ShadCN, it was a very, very popular component framework. Over in the components directory, we can see that you have a directory location that it's going to put its components into.

    So if you want to have your own shared components and also ShadCN shared components, then let me go show you how to do that. So I'm going to add on ShadCN. And then over in our components directory, I'm going to create a new folder called ShadCN at the top level. And into there, I'm going to put that UI directory. Now ShadCN is in there.

    Let's go take a look at our page. I'll need to use ShadCN UI button. Now, let's see, can I add a ShadCN component? So over my terminal, I'm going to go to apps, app1 and I'm going to use ShadCN at the latest to add the separator component. All right, cool.

    Now, let's put the separator in that directory. So ShadCN is still happy. We've just gone and overwritten where it's going to put its components. So let's go and take our component then and put it into the top level of the components directory. Get rid of our components inside of page.tsx.

    Now, I can use add components heading there in my subpage. I can also bring in the page and use the heading there. Let's try it out. Again, I'll go back to the top level, run pmpm-dev so you run both applications simultaneously. So now we've got that heading used in both the homepage route and also in the subpage route And I think we've actually put that heading component in the right place.

    That heading is now in the source components directory, which is a strong indication to me that I'm supposed to reuse this anywhere throughout the application. I could further refine that by creating a new folder, say, called design system, which would be our local design system, and drop that into there. That's certainly a way if you want to keep things a little bit more organized, that's certainly pretty valid. Or you could do it functionally, you could have a component directory of layout components or header components, whatever you want. Now, of course, what if we want to go and take this heading and actually share it between app one and app two?

    Well, that's why this is in a TurboRepo. So let's go and take our heading and simply drag it all the way down to packages. And then inside of packages, UI, and then inside of UI source, and I drop my heading in there. All right, now everything's broken, that's fine. Also, over in tsconfig, we don't need to do that.

    So Thank you very much, BS Code, you didn't need to go all that far. Now let's go over to Packages.UI and take a look at how that works. Now one thing you notice right away is heading is uppercased here where other components, say button, card, code, those are all lowercased. And that's something you're actually going to want to decide for your project fairly early on is whether you want to uppercase name or lowercase name your component files. I personally default to what I've considered the React standard for a bunch of years which is that components are in uppercase files but you can decide for you what you want to do in your project.

    So I'm just going to follow the pattern here, which I think is important. So I'm going to take that, turn that into heading, and the way that we expose that is by adding onto our package JSON, heading, and now back in our apps, to our package.json heading. Now, back in our apps, say app1 in our page, We can instead get our heading from repo UI heading. The same thing in our subpage. Make sure that all still works.

    Hit refresh, refresh. That looks good, but let's change the color just to make sure that it is getting that one. So I'll change that to green. And there we go. Now I've got green.

    Awesome. So now let's get that over into our app two. To do that, we'll go into our app one. We'll grab our import, copy it, go to app two, into source and the page directory, heading, Let's save. There we go.

    Now, I've got that component reused across all of our applications. More importantly, the component is in the right spot now. It's inside of that package's UI, inside that source, in the right spot, and with the right naming. Of course, if you have any stories or tests associated with that heading, you'd want those also to be in that package UI directory. Long story short, I highly recommend that your components are as close as possible to the code that actually uses them.

    That means that if they are route-specific components, they would be in the route directory. If they're application-specific components, they would be in an components directory inside of source. And then finally, if you wanted to use them between applications, they would be in a shared npm package, whether you manually do that using single repos and then NPM linking, or in this case, a monorepo like Turbo Repo here.