Components structure in Umami codebase - Part 1.1
Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.
This article focuses only on the components structure in Umami codebase.
Prerequisite
- Components structure in Umami codebase — Part 1.0
Approach
The approach we take is simple:
-
Pick a route, for example, https://cloud.umami.is/analytics/us/websites
-
Locate this route in Umami codebase
-
Review how the components are imported and organized.
-
We repeat this process for 3–4 pages to establish a common pattern, see if there’s any exceptions.
In this part 1.1, we review the websites route and see what components make up the websites page and how they are imported and how they are organized.

We have to review the following files:
page.tsx
You will find the below code in websites/page.tsx.
import { WebsitesPage } from './WebsitesPage';
import { Metadata } from 'next';
export default function () {
return <WebsitesPage />;
}
export const metadata: Metadata = {
title: 'Websites',
};
There isn’t much code here, in fact, it is only 10 lines. The best projects always keep it simple, nothing complicated.
If you define methods like fetching list of websites or a method to create a website in the page.tsx itself and then pass this down as a prop to the components, then you are doing something wrong.
WebsitesPage is imported from websites folder where the page.tsx is located. This is colocating your components.
WebsitesPage.tsx
Even the WebsitesPage.tsx only has 26 lines.
'use client';
import { WebsitesDataTable } from './WebsitesDataTable';
import { WebsiteAddButton } from './WebsiteAddButton';
import { useMessages, useNavigation } from '@/components/hooks';
import { Column } from '@umami/react-zen';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { PageBody } from '@/components/common/PageBody';
export function WebsitesPage() {
const { teamId } = useNavigation();
const { formatMessage, labels } = useMessages();
return (
<PageBody>
<Column gap="6" margin="2">
<PageHeader title={formatMessage(labels.websites)}>
<WebsiteAddButton teamId={teamId} />
</PageHeader>
<Panel>
<WebsitesDataTable teamId={teamId} />
</Panel>
</Column>
</PageBody>
);
}
WebsitesDataTable and WebsiteAddButton are colocated. However, PageHeader, Panel and PageBody are imported from common folder. This is because of the way Umami website looks like. If you visit websites, you will see a header:

Similarly, if you visit Links page, you will see the header containing Links with a button.

Do you see the pattern here? these can be abstracted to a common component and reused. This is what you see in Umami’s codebase.
We will review the PageHeader, PageBody and Panel, WebsitesDataTable and WebsiteAddButton in the next part.
About me:
Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.
Email: ramu.narasinga@gmail.com
I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com