API layer in Umami codebase - Part 1.4
Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.
This article focuses only on the API layer in Umami codebase.
Prerequisites
-
API layer in Umami codebase — Part 1.0
-
API layer in Umami codebase — Part 1.1
-
API layer in Umami codebase — Part 1.2
-
API layer in Umami codebase — Part 1.3
In the parts 1.1, 1.2 we reviewed API layer in /websites and /links page and in part 1.3, we reviewed the website overview page. In this article, we review links detail page.

In the Link detail page, you will find the below code:
'use client';
import { PageBody } from '@/components/common/PageBody';
import { LinkProvider } from '@/app/(main)/links/LinkProvider';
import { LinkHeader } from '@/app/(main)/links/[linkId]/LinkHeader';
import { Panel } from '@/components/common/Panel';
import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import { LinkMetricsBar } from '@/app/(main)/links/[linkId]/LinkMetricsBar';
import { LinkControls } from '@/app/(main)/links/[linkId]/LinkControls';
import { LinkPanels } from '@/app/(main)/links/[linkId]/LinkPanels';
import { Column, Grid } from '@umami/react-zen';
import { ExpandedViewModal } from '@/app/(main)/websites/[websiteId]/ExpandedViewModal';
const excludedIds = ['path', 'entry', 'exit', 'title', 'language', 'screen', 'event'];
export function LinkPage({ linkId }: { linkId: string }) {
return (
<LinkProvider linkId={linkId}>
<Grid width="100%" height="100%">
<Column margin="2">
<PageBody gap>
<LinkHeader />
<LinkControls linkId={linkId} />
<LinkMetricsBar linkId={linkId} showChange={true} />
<Panel>
<WebsiteChart websiteId={linkId} />
</Panel>
<LinkPanels linkId={linkId} />
</PageBody>
<ExpandedViewModal websiteId={linkId} excludedIds={excludedIds} />
</Column>
</Grid>
</LinkProvider>
);
}
So let’s review the below two components:
-
LinkMetricsBar
-
LinkPanels
LinkMetricsBar
In the LinkMetricsBar, you will find the following code at L17
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(linkId);
This is the same API used in the website overview page. Check out the API layer part 1.3.
LinkPanels
LinkPanels is an interesting component. You will find the below code from L39–54
<Tabs>
<TabList>
<Tab id="browser">{formatMessage(labels.browsers)}</Tab>
<Tab id="os">{formatMessage(labels.os)}</Tab>
<Tab id="device">{formatMessage(labels.devices)}</Tab>
</TabList>
<TabPanel id="browser">
<MetricsTable type="browser" title={formatMessage(labels.browser)} {...tableProps} />
</TabPanel>
<TabPanel id="os">
<MetricsTable type="os" title={formatMessage(labels.os)} {...tableProps} />
</TabPanel>
<TabPanel id="device">
<MetricsTable type="device" title={formatMessage(labels.device)} {...tableProps} />
</TabPanel>
</Tabs>
To understand the API layer here, we need to review the MetricsTable because I did not see any references to Tanstack query hooks in LinkPanels.ts
MetricsTable
MetricsTable is used to fetch a lot of data in the Link detail page, depending on the props passed down to it.
In this MetricTable component, at L35, you will find the below code:\
const { data, isLoading, isFetching, error } = useWebsiteMetricsQuery(websiteId, {
type,
limit,
...params,
});
useWebsiteMetricsQuery is a common query used to fetch information depending on the type and other parameters, using our code snippet, here type could be browser, os, device.
You will find the below code in useWebsiteMetricsQuery.ts
import { ReactQueryOptions } from '@/lib/types';
import { keepPreviousData } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useDateParameters } from '../useDateParameters';
import { useFilterParameters } from '../useFilterParameters';
export type WebsiteMetricsData = {
x: string;
y: number;
}[];
export function useWebsiteMetricsQuery(
websiteId: string,
params: { type: string; limit?: number; search?: string },
options?: ReactQueryOptions<WebsiteMetricsData>,
) {
const { get, useQuery } = useApi();
const { startAt, endAt, unit, timezone } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteMetricsData>({
queryKey: [
'websites:metrics',
{
websiteId,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
},
],
queryFn: async () =>
get(`/websites/${websiteId}/metrics`, {
startAt,
endAt,
unit,
timezone,
...filters,
...params,
}),
enabled: !!websiteId,
placeholderData: keepPreviousData,
...options,
});
}
Conclusion
We reviewed the Link detail page and found the same API used in the website detail page to fetch similar information like views and stats. However, the metrics in the other sections is fetched and rendered using a common component named MetricsTable and this data is fetched depending on the type.
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
References:
-
https://github.com/umami-software/umami/blob/master/src/app/(main)/links/%5BlinkId%5D/LinkPage.tsx
-
https://github.com/umami-software/umami/blob/master/src/components/metrics/MetricsTable.tsx#L35