Components structure in Umami codebase - Part 1.2
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
-
Components structure in Umami codebase — Part 1.1
In Part 1.1, we reviewed the page.tsx and the WebsitesPage.tsx. In this article, we review:
PageHeader, PageBody and Panel
In the WebsitesPage.tsx, you will find the following import:
import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; import { PageBody } from '@/components/common/PageBody';
You will find these components located at src/components/common

These “commonly” used components are abstracted and placed in components so they can be reused.
WebsitesDataTable.tsx
WebsitesDataTable.tsx is located in the same folder as page.tsx. and this component has the following definition:
import Link from 'next/link'; import { WebsitesTable } from './WebsitesTable'; import { DataGrid } from '@/components/common/DataGrid'; import { useLoginQuery, useNavigation, useUserWebsitesQuery } from '@/components/hooks'; export function WebsitesDataTable({ userId, teamId, allowEdit = true, allowView = true, showActions = true, }: { userId?: string; teamId?: string; allowEdit?: boolean; allowView?: boolean; showActions?: boolean; }) { const { user } = useLoginQuery(); const queryResult = useUserWebsitesQuery({ userId: userId || user?.id, teamId }); const { renderUrl } = useNavigation(); const renderLink = (row: any) => ( <Link href={renderUrl(`/websites/${row.id}`, false)}>{row.name}</Link> ); return ( <DataGrid query={queryResult} allowSearch allowPaging> {({ data }) => ( <WebsitesTable data={data} showActions={showActions} allowEdit={allowEdit} allowView={allowView} renderLink={renderLink} /> )} </DataGrid> ); }
This is where the websites are fetched. Notice how the table rendering is dealt with in another component named WebsitesTable.
WebsiteAddButton.tsx
You will find the code in WebsiteAddButton.tsx:
import { useMessages, useModified } from '@/components/hooks'; import { useToast } from '@umami/react-zen'; import { Plus } from '@/components/icons'; import { WebsiteAddForm } from './WebsiteAddForm'; import { DialogButton } from '@/components/input/DialogButton'; export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { toast } = useToast(); const { touch } = useModified(); const handleSave = async () => { toast(formatMessage(messages.saved)); touch('websites'); onSave?.(); }; return ( <DialogButton icon={<Plus />} label={formatMessage(labels.addWebsite)} variant="primary" width="400px" > {({ close }) => <WebsiteAddForm teamId={teamId} onSave={handleSave} onClose={close} />} </DialogButton> ); }
We have some new findings here.
-
Plus is an icon imported from
components/iconsfolder. -
DialogButton is imported from
components/input/DialogButton
Why not DialogButton be part of common folder? This is about colocating and since this is in input foler, we can expect other input related components located in this folder as shown in the below screenshot.

In the next article, we review the website detail page.

About me:
Hey, my name is Ramu Narasinga. Email: ramu.narasinga@gmail.com
Tired of AI-generated code that works but nobody understands?
I spent 3+ years studying OSS codebases and wrote 350+ articles on what makes them production-grade. I built an open source tool that reviews your PR against your existing codebase patterns.
Your codebase. Your patterns. Enforced.
References:
-
https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/WebsiteAddButton.tsx
-
https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/WebsitesPage.tsx#L6
-
https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/WebsitesTable.tsx
-
https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/WebsitesDataTable.tsx#L6