@statsbygg/layout
v0.1.17
Published
Shared layout components for Statsbygg microfrontend architecture
Downloads
140
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
npm install
# Build the package
npm run build
# Watch mode for development
npm run dev
# Type checking
npm run type-check
# Linting
npm run lintDependencies
This package includes:
@digdir/designsystemet-react^1.5.1@statsbygg/design-tokens^0.2.0clsx^2.0.0zustand^5.0.4
License
Internal use only - Statsbygg
