DL
Back to Blog
TechFebruary 3, 2026·5 min read

Building a FHIR Frontend with Next.js

How FhirHub's Next.js 16 frontend is structured with 60+ components, App Router route groups, DaisyUI theming, and a layered architecture that keeps FHIR complexity out of the UI.

D

David Le

Building a FHIR Frontend with Next.js

By David Le -- Part 11 of the FhirHub Series

FhirHub's frontend is a Next.js 16 application with 60+ React components organized across 12 categories. It uses the App Router with route groups to separate authentication flows from the dashboard, DaisyUI for theming, and a layered architecture that keeps FHIR complexity out of the UI.

App Router Structure

The frontend uses Next.js App Router with two route groups:

frontend/src/app/
├── (auth)/                    # Public authentication routes
│   ├── login/page.tsx
│   └── register/page.tsx
├── (dashboard)/               # Protected dashboard routes
│   ├── dashboard/page.tsx
│   ├── patients/
│   │   ├── page.tsx           # Patient list
│   │   ├── new/page.tsx       # Create patient
│   │   └── [id]/page.tsx      # Patient detail
│   ├── export/page.tsx        # Bulk data export
│   ├── alerts/page.tsx        # Clinical alerts
│   ├── activity/page.tsx      # Activity feed
│   ├── smart-launch/page.tsx  # SMART on FHIR tools
│   └── admin/
│       ├── users/
│       │   ├── page.tsx       # User management
│       │   └── [id]/page.tsx  # User detail
│       └── audit/page.tsx     # Audit logs
└── page.tsx                   # Root redirect

Route groups (parentheses in folder names) let us apply different layouts without affecting the URL:

  • (auth) uses a minimal layout (no sidebar, no header)
  • (dashboard) uses the full application layout with sidebar navigation, header, and role-based menu items

Component Library

The 60+ components are organized into 12 categories:

CategoryCountExamples
ui/8Button, Badge, Modal, Toast, LoadingSkeleton, Icons
patients/12PatientTable, PatientGrid, VitalsChart, LabsPanel, TimelineView
dashboard/5MetricsRow, ActivityFeed, AlertsPanel, RecentPatientsList, SystemStatus
forms/5FormField, DatePicker, FilterPills, SelectDropdown, SearchInput
export/2ExportWizard, ExportJobList
smart/3LaunchSimulator, TokenInspector, ScopeVisualizer
layout/3AppLayout, Header, Sidebar
common/4DataTable, PatientCard, StatsCard, PatientSelector
fhir/3IdentifierDisplay, CodingDisplay, FhirDate
errors/3ErrorBoundary, ApiErrorDisplay, ResourceNotFound
auth/2RoleGate, RouteGuard
admin/5UserTable, UserRoleEditor, InviteUserModal, UserSessionsPanel, AuditLogTable

FHIR-Specific Components

The fhir/ category contains components that understand FHIR data structures:

  • IdentifierDisplay -- Renders FHIR Identifier types with system and value
  • CodingDisplay -- Renders CodeableConcept with system, code, and display
  • FhirDate -- Handles FHIR date, dateTime, and instant formats

These encapsulate FHIR display logic so patient-facing components don't need to parse FHIR structures.

Auth Components

  • RouteGuard -- Wraps protected routes, redirects unauthenticated users to login
  • RoleGate -- Conditionally renders content based on the user's Keycloak roles
<RoleGate roles={["admin", "practitioner"]}>
  <AddConditionButton />
</RoleGate>

Layered Architecture

The frontend follows a clean layered architecture:

Components (UI layer)
    ↓
Hooks (state management)
    ↓
Services (business logic)
    ↓
Repositories (API client)
    ↓
.NET API Gateway

Repositories

Repositories handle HTTP communication with the API:

frontend/src/repositories/api/
├── export.repository.ts
└── ...

Each repository maps to an API controller and returns typed DTOs.

Services

Services contain business logic that's independent of UI:

frontend/src/services/
├── export.service.ts
└── ...

Services orchestrate repository calls, handle caching, and transform data.

Custom Hooks

Hooks connect services to React components, managing loading states, errors, and data refresh:

frontend/src/hooks/
├── use-patients.ts
├── use-vitals.ts
└── ...

Styling with Tailwind CSS 4 + DaisyUI 5

FhirHub uses Tailwind CSS 4 with DaisyUI 5 for a consistent, accessible design system:

  • DaisyUI provides pre-built components (buttons, badges, modals, tabs) with semantic class names
  • Tailwind handles custom spacing, responsive breakpoints, and utility classes
  • tailwind-merge resolves class conflicts when components accept custom classNames:
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: (string | undefined | false)[]) {
  return twMerge(clsx(inputs));
}

DaisyUI's theming system means the entire app can switch themes (light, dark, etc.) with a single class change.

Validation with Zod

Frontend form validation uses Zod for runtime type checking:

// Zod schemas validate data at runtime
import { z } from "zod";

const PatientSchema = z.object({
  name: z.string().min(1, "Name is required"),
  birthDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Invalid date format"),
  gender: z.enum(["male", "female", "other", "unknown"]),
});

Zod is used alongside FluentValidation on the backend -- defense in depth with validation at both layers.

Key Pages

Dashboard (/dashboard)

The main landing page combines multiple data sources:

  • Metrics row (patient count, observation count, etc.)
  • Recent patients list
  • Activity feed (latest clinical events)
  • Alerts panel (abnormal values)
  • System status (service health)
  • Quick actions (common tasks)

Patient Detail (/patients/[id])

The most complex page, with tabbed navigation:

  • Overview -- Patient header with demographics
  • Vitals -- Interactive chart + recording modal
  • Conditions -- List with add condition modal
  • Medications -- List with add medication modal
  • Labs -- Panels with reference range highlighting
  • Timeline -- Chronological event view

Export (/export)

Two-part page:

  1. Export wizard (create new export)
  2. Export job list (monitor existing exports)

SMART Launch (/smart-launch)

Developer tools for SMART on FHIR:

  • Launch simulator
  • Token inspector
  • Scope visualizer

What's Next

In Part 12, we'll examine Keycloak configuration -- realm setup, client configuration with PKCE, the role hierarchy, and claims transformation.


Find the source code on GitHub Connect on LinkedIn

Related Projects

Featured

FhirHub

A healthcare data management platform built on the HL7 FHIR R4 standard, providing a comprehensive web interface for managing patient clinical data including vitals, conditions, medications, lab orders, and bulk data exports with role-based access control and full audit logging.

Next.js 16
React 19
Typescript
Tailwind CSS 4
+8