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.
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:
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.