@statsbygg/layout
v0.2.10
Published
Shared layout components for Statsbygg microfrontend architecture
Keywords
Readme
@statsbygg/layout
Self-contained shared layout package for Statsbygg's Next.js microfrontend architecture using DigDir Designsystemet.
Overview
This package provides a consistent layout and state management across multiple independent Next.js applications (zones) in a microfrontend architecture.
Features
- DigDir Designsystemet Integration: Uses official Norwegian design system components
- Self-Contained State Management: Built-in zustand store for global state (user, theme, locale)
- Dynamic Breadcrumbs: Automatic breadcrumb generation with responsive behavior
- Cross-Zone Synchronization: State persists across browser tabs and zone navigation
- CSS Modules with Colocation: Each component follows ComponentName/ComponentName.tsx pattern
- TypeScript: Full type safety with separate .types.ts files
- Function Declarations: All functions use function declaration syntax
Installation
npm install @statsbygg/layoutPeer Dependencies
This package requires:
next>= 14.0.0react>= 18.0.0react-dom>= 18.0.0
Package Structure
src/
├── components/
│ ├── Breadcrumbs/
│ │ ├── Breadcrumbs.tsx
│ │ ├── Breadcrumbs.types.ts
│ │ ├── Breadcrumbs.module.css
│ │ └── index.ts
│ ├── GlobalHeader/
│ │ ├── GlobalHeader.tsx
│ │ ├── GlobalHeader.types.ts
│ │ ├── GlobalHeader.module.css
│ │ └── index.ts
│ ├── GlobalFooter/
│ │ ├── GlobalFooter.tsx
│ │ ├── GlobalFooter.types.ts
│ │ ├── GlobalFooter.module.css
│ │ └── index.ts
│ └── RootLayout/
│ ├── RootLayout.tsx
│ ├── RootLayout.types.ts
│ ├── RootLayout.module.css
│ └── index.ts
├── store/
│ └── globalState.ts
├── utils/
│ └── routeRegistry.ts
└── index.tsUsage
1. Defining Your Route Tree
The routing system uses URL-Driven Logic. You define a single RouteNode tree passed to RootLayout.
- External Links: Any path starting with
httporhttpsis treated as an external parent. - Internal Links: Any relative path (e.g.,
/,/about) is treated as internal to your application (relative to yourbasePath).
Scenario A: Standard Application
This app sits directly under the main Statsbygg site.
// app/layout.tsx
import { RootLayout, RouteNode } from "@statsbygg/layout";
const ROUTES: RouteNode = {
label: "Hjem",
path: "https://statsbygg.no", // External Parent
children: [
{
label: "My App",
path: "/", // App Root (relative to basePath)
children: [
{ label: "Page 1", path: "/page-1" },
{ label: "Page 2", path: "/page-2" },
],
},
],
};
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="no">
<body>
<RootLayout routes={ROUTES}>{children}</RootLayout>
</body>
</html>
);
}Scenario B: Deeply Nested Microfrontend
This app sits deep within a hierarchy of other applications.
// app/layout.tsx
const ROUTES: RouteNode = {
label: "Hjem",
path: "https://statsbygg.no",
children: [
{
label: "Parent Route",
path: "https://statsbygg.no/parent-route", // External Parent 2
children: [
{
label: "Your App Name",
path: "/", // Your App Root
children: [{ label: "Your App Route", path: "/your-app-route" }],
},
],
},
],
};2. Handling Dynamic Routes
For pages with dynamic IDs (e.g., /properties/[id]), use the useBreadcrumbs hook.
Note: You only need to define the dynamic portion of the path. The layout package automatically prepends the static parents (Home, App Root, etc.) defined in your ROUTES tree.
// app/properties/[id]/page.tsx
"use client";
import { useBreadcrumbs } from "@statsbygg/layout";
export default function PropertyPage({ params, propertyName }) {
useBreadcrumbs([
{ label: "Properties", href: "/properties" },
{ label: propertyName, href: `/properties/${params.id}` },
]);
return <div>...</div>;
}
// Resulting Breadcrumbs: Home > My App > Properties > [Property Name]Using the Global Store
The package exports a zustand store for managing global state:
import { useGlobalStore } from "@statsbygg/layout";
function MyComponent() {
const { user, theme, setUser, setTheme } = useGlobalStore();
function handleLogin() {
setUser({ name: "John Doe", email: "[email protected]" });
}
function toggleTheme() {
setTheme(theme === "light" ? "dark" : "light");
}
return (
<div>
<p>Current theme: {theme}</p>
{user && <p>Logged in as {user.name}</p>}
<button onClick={toggleTheme}>Toggle theme</button>
</div>
);
}API Reference
RootLayout
Main layout component that orchestrates the entire layout structure.
interface RootLayoutProps {
children: React.ReactNode;
routes: RouteNode;
className?: string;
}useGlobalStore
Zustand store hook for global state management.
interface GlobalState {
user: { name: string; email: string } | null;
theme: "light" | "dark";
locale: "no" | "en";
setUser: (user: { name: string; email: string } | null) => void;
setTheme: (theme: "light" | "dark") => void;
setLocale: (locale: "no" | "en") => void;
initialize: () => Promise<void>;
}Breadcrumbs Behavior
The Breadcrumbs component uses Designsystemet's responsive behavior:
- On narrow screens (<650px): Shows a back button to parent level
- On wide screens (≥650px): Shows full breadcrumb path
- Last item: Automatically marked with
aria-current="page"
State Persistence
Global state is automatically persisted to localStorage using zustand's persist middleware. This enables:
- Cross-tab synchronization: Changes in one tab reflect in others
- Cross-zone persistence: State is maintained when navigating between zones
- Session persistence: State survives page refreshes
Design System Integration
This package uses DigDir Designsystemet components:
Breadcrumbs(with List, Item, Link)ButtonHeadingLinkParagraph
All styling uses Designsystemet design tokens:
--ds-spacing-*for spacing--ds-color-*for colors--ds-font-size-*for typography
Development
# Install dependencies
pnpm install
# Build the package
pnpm build
# Watch mode for development
pnpm dev
# Type checking
pnpm type-check
# Linting
pnpm lintDependencies
This package includes:
@digdir/designsystemet-react1.12.1@statsbygg/design-tokens^0.2.0clsx^2.0.0zustand^5.0.4
License
Internal use only - Statsbygg
