Frontend Architecture
Relevant source files
The following files were used as context for generating this wiki page:The Umami frontend is a modern web application built with Next.js and React. It serves as the primary user interface for viewing analytics data, managing websites, teams, and user settings. The architecture leverages the Next.js App Router for a structured, server-centric approach to rendering and routing, combined with a rich set of client-side components for interactivity.
The core of the application is designed to be data-driven, utilizing @tanstack/react-query for efficient data fetching, caching, and state synchronization with the backend API. The UI is constructed using a custom component library, @umami/react-zen, ensuring a consistent look and feel across the platform. The architecture is modular, with clear separation of concerns between page layouts, page content, and shared components.
Technology Stack
The frontend is built upon a foundation of modern JavaScript technologies. The primary dependencies define its core capabilities, from the web framework to data fetching and UI components.
| Library/Framework | Version | Description |
|---|---|---|
next | ^15.5.9 | The React framework for server-side rendering, static site generation, and routing (App Router). |
react | ^19.2.3 | The core library for building the user interface. |
@tanstack/react-query | ^5.90.11 | A library for fetching, caching, and managing server state. Used extensively via custom hooks. |
@umami/react-zen | ^0.211.0 | A custom component library providing UI elements like Grid, Column, Button, Panel, etc. |
zustand | ^5.0.9 | A small, fast, and scalable state-management solution for client-side state. |
chart.js | ^4.5.1 | A library for creating responsive and interactive charts to visualize analytics data. |
react-intl | ^7.1.14 | Used for internationalization (i18n) and formatting messages, dates, and numbers. |
Sources: package.json:71-110
Project Structure and Routing
The application utilizes the Next.js App Router, which organizes the frontend by directories. Key architectural patterns include route groups and dynamic routes.
Root Layout
The entry point for the HTML document is src/app/layout.tsx. It sets up the <html> and <body> tags, includes global stylesheets, fonts, and metadata. It also wraps all children in a Providers component, which likely sets up contexts for theming, data fetching, and internationalization.
// src/app/layout.tsx:13-40
export default function ({ children }) {
// ...
return (
<html lang="en">
<head>
{/* ... meta tags, icons, etc. */}
</head>
<body>
<Suspense>
<Providers>{children}</Providers>
</Suspense>
</body>
</html>
);
}
Sources: src/app/layout.tsx:13-40
Main Application Layout
The (main) directory is a Next.js Route Group. It defines a shared layout for the authenticated part of the application without affecting the URL path.
src/app/(main)/layout.tsx: This layout simply wraps its children with the<App>component.src/app/(main)/App.tsx: This is the core application shell. It performs an authentication check using theuseLoginQueryhook. If the user is not authenticated, it redirects to the login page. For authenticated users, it renders the main application structure, including aSideNavfor desktop and aMobileNavfor smaller viewports.
This diagram illustrates the initial application load and authentication flow.
Nested Layouts for Features
Features like "Websites" have their own nested layouts. For example, src/app/(main)/websites/[websiteId]/layout.tsx defines the structure for a single website's view. This layout renders a WebsiteLayout component which in turn provides a WebsiteNav and a WebsiteHeader.
This component hierarchy shows how layouts are nested to build the final page view.
Data Fetching and State Management
The application relies on @tanstack/react-query for managing server state. This is implemented through custom hooks that abstract the API calls.
useLoginQuery(): Fetches the current user's data to manage authentication state.
Sources: src/app/(main)/App.tsx:12-12useWebsiteQuery(websiteId): Fetches details for a specific website.
Sources: src/app/(main)/console/[websiteId]/TestConsolePage.tsx:12-12useResultQuery(type, params): A generic hook for fetching report data, used in components likeUTM.tsxandGoal.tsx.
Sources: src/app/(main)/websites/[websiteId]/(reports)/utm/UTM.tsx:19-23, src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx:26-30
Client-side state is managed with zustand, as indicated by its inclusion in package.json.
Sources: package.json:110-110
Configuration (next.config.ts)
The next.config.ts file is central to the application's build and runtime behavior. It defines environment variables, security headers, and routing rules.
Environment Variables
Several environment variables are exposed to the client-side bundle.
| Variable | Description |
|---|---|
basePath | A path prefix for the application URL. |
cloudMode | A flag to enable or disable cloud-specific features. |
cloudUrl | The base URL for the cloud version of Umami. |
currentVersion | The application version, sourced from package.json. |
defaultLocale | The default language for the application. |
Sources: next.config.ts:161-168
HTTP Headers and Security
The configuration sets up a strict Content Security Policy (CSP) to mitigate XSS attacks. It also defines CORS headers for the API and tracker script endpoints.
// next.config.ts:15-22
const contentSecurityPolicy = `
default-src 'self';
img-src 'self' https: data:;
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self' https:;
frame-ancestors 'self' ${frameAncestors};
`;
Sources: next.config.ts:15-22
Rewrites and Redirects
Rewrites are used to proxy requests or map user-friendly URLs to internal paths.
- The tracker script can be served under alternative names (
trackerScriptName). - A custom
collectApiEndpointcan be mapped to the standard/api/sendendpoint. - Team-specific URLs like
/teams/:teamId/websitesare rewritten to/websites, with theteamIdlikely being handled by a context or hook.
This flow demonstrates how next.config.ts handles an incoming request for a custom tracker script.
Sources: next.config.ts:133-145, next.config.ts:182-185
Analytics Tracker Integration
The frontend includes a TestConsolePage for testing the Umami tracker script (script.js). This page demonstrates how to integrate the tracker and send custom events.
The tracker script is loaded via a Next.js <Script> component, with the data-website-id attribute dynamically set.
// src/app/(main)/console/[websiteId]/TestConsolePage.tsx:114-119
<Script
async
data-website-id={websiteId}
src={`${process.env.basePath || ''}/script.js`}
data-cache="true"
/>
Sources: src/app/(main)/console/[websiteId]/TestConsolePage.tsx:114-119
Firing Tracking Events
Events can be fired declaratively using data-umami-event attributes on HTML elements, or programmatically using the window.umami object. The test console provides examples of both, including tracking page views, custom events with and without data, revenue, and identifying users.
This diagram shows the sequence for sending a programmatic tracking event.
Conclusion
The Umami frontend architecture is a robust and scalable system built on the Next.js App Router. It effectively separates concerns by using nested layouts for structure, a dedicated component library (@umami/react-zen) for UI, and a powerful data-fetching layer with @tanstack/react-query. Configuration via next.config.ts provides flexibility for deployment and security, while the clear patterns for routing and state management make the codebase maintainable and extensible. This architecture is well-suited for a data-intensive application, providing a fast, interactive, and developer-friendly experience.