page
Guide to building Next.js page shells — metadata, params, header pattern selection, content wrappers, and structural concerns. Pages are thin shells; content is handled by other skills.
What this skill does
# Page — Shell Guide
Pages are thin shells providing metadata, a header, and a content wrapper. They do NOT own what goes inside the wrapper — forms, lists, task groups, and inquiry content are handled by other skills.
## Metadata
Every page exports a static `metadata` object. Title is always a plain German string — never use translations.
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
export const metadata: Metadata = {
title: `Seitentitel | ${Constants.companyName}`,
};
```
## Params & SearchParams
### Params (dynamic route segments)
Derive from the file path — every `[segment]` becomes a param. Always `Promise`, always `await` before destructuring.
```tsx
// Path: src/app/operations/weg-konten/[financingCaseId]/verrechnungskonto/page.tsx
type Props = {
params: Promise<{ financingCaseId: string }>;
};
export default async function SomePage({ params }: Props) {
const { financingCaseId } = await params;
// ...
}
```
Multiple params accumulate from all segments in the path:
```tsx
// Path: src/app/operations/anfragen/[inquiryId]/[stepId]/page.tsx
type Props = {
params: Promise<{ inquiryId: string; stepId: string }>;
};
```
### SearchParams (list pages and auth pages only)
**List pages** — use nuqs `SearchParams` type with a search params cache:
```tsx
import { SearchParams } from "nuqs/server";
type Props = {
searchParams: Promise<SearchParams>;
};
export default async function ListPage({ searchParams }: Props) {
const resolvedSearchParams = await searchParams;
const { search, pagination } = someSearchParamsCache.parse(resolvedSearchParams);
// ...
}
```
**Auth pages** — use explicit typed searchParams:
```tsx
type Props = {
searchParams: Promise<{
passwordResetSuccess: string;
redirectTo: string;
}>;
};
export default async function AuthPage(props: Props) {
const searchParams = await props.searchParams;
// ...
}
```
## Header Pattern Decision Tree
Choose the correct header based on page context:
1. **Inquiry step page?** (inside an inquiry process) → **No header in page.tsx**. The page is a thin wrapper that delegates to a feature page component. The feature component renders `InquiryHeader` + `InquiryContent`. See the [inquiry-process step-page guide](../inquiry-process/step-page.md).
2. **Auth page?** (login, password reset, etc.) → **No header**. Use `Panel` + `VStack` as container.
3. **Sub-page under a detail page?** (e.g., `[id]/verrechnungskonto/page.tsx`) → `PageHeader` with `PageHeaderBackButton` + `PageHeaderTitle`. Often uses a shared header component like `FspFinancingCaseOverviewSubPageHeader`.
4. **List page or standard portal page?** → `PageHeader` with `PageHeaderTitle` + optional `PageHeaderActions`.
5. **Detail overview page?** (e.g., `[id]/page.tsx` showing status/tasks) → Custom header component extracted to a feature, built on `PageHeader` primitives.
For reusable header components, when to use them vs composing inline, and where to store new ones, see [headers.md](./headers.md).
## Content Wrappers
- **`PageContent`** — standard portal pages (list, overview, sub-page form). Import from `@finstreet/ui/components/pageLayout/PageContent`.
- **`InquiryContent`** — inquiry process steps. Used inside the feature page component, not in `page.tsx`. Import from `@finstreet/ui/components/pageLayout/InquiryContent`.
- **`Panel`** — auth pages. No `PageContent` needed. Import from `@finstreet/ui/components/base/Panel`.
Content inside these wrappers is provided by other skills.
## Page Shell Templates
### Template 1: Standard Portal Page (most common)
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
import {
PageHeader,
PageHeaderActions,
PageHeaderTitle,
} from "@finstreet/ui/components/pageLayout/PageHeader";
import { Headline } from "@finstreet/ui/components/base/Headline";
import { PageContent } from "@finstreet/ui/components/pageLayout/PageContent";
import { getExtracted } from "next-intl/server";
export const metadata: Metadata = {
title: `Seitentitel | ${Constants.companyName}`,
};
export default async function {PageName}Page() {
const t = await getExtracted();
return (
<>
<PageHeader>
<PageHeaderTitle>
<Headline as="h1">{t("{German title}")}</Headline>
</PageHeaderTitle>
<PageHeaderActions>{/* Action buttons */}</PageHeaderActions>
</PageHeader>
<PageContent>
{/* Content from other skills */}
</PageContent>
{/* Modals at the bottom */}
</>
);
}
```
### Template 2: List Page with SearchParams
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
import { PageContent } from "@finstreet/ui/components/pageLayout/PageContent";
import { SearchParams } from "nuqs/server";
import { Suspense } from "react";
import { ListSkeleton } from "@finstreet/ui/components/base/Skeletons/ListSkeleton";
export const metadata: Metadata = {
title: `Seitentitel | ${Constants.companyName}`,
};
export const dynamic = "force-dynamic";
type Props = {
searchParams: Promise<SearchParams>;
};
export default async function {PageName}Page({ searchParams }: Props) {
const resolvedSearchParams = await searchParams;
const { search, pagination } = {feature}SearchParamsCache.parse(resolvedSearchParams);
return (
<>
{/* PageHeader with title + optional actions */}
<PageContent>
<Suspense fallback={<ListSkeleton />}>
{/* List component */}
</Suspense>
</PageContent>
{/* Modals at the bottom */}
</>
);
}
```
### Template 3: Sub-Page with Back Button
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
import { FspFinancingCaseOverviewSubPageHeader } from "@/layouts/fsp/FspFinancingCaseOverviewSubPageHeader";
import { PageContent } from "@finstreet/ui/components/pageLayout/PageContent";
export const metadata: Metadata = {
title: `Seitentitel | ${Constants.companyName}`,
};
type Props = {
params: Promise<{ financingCaseId: string }>;
};
export default async function {PageName}Page({ params }: Props) {
const { financingCaseId } = await params;
return (
<>
<FspFinancingCaseOverviewSubPageHeader
title={t("formTitle")}
financingCaseId={financingCaseId}
header={response.header}
/>
<PageContent>
{/* Content from other skills */}
</PageContent>
</>
);
}
```
When no shared header component exists, compose directly with `PageHeader` primitives:
```tsx
<PageHeader>
<PageHeaderBackButton href={backUrl}>{t("back")}</PageHeaderBackButton>
<PageHeaderTitle>
<Headline as="h1">{title}</Headline>
</PageHeaderTitle>
</PageHeader>
```
### Template 4: Inquiry Step Page (thin wrapper)
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
import { {StepName}Page } from "@/features/{purpose}InquiryProcess/components/{StepName}Page";
export const metadata: Metadata = {
title: `{Step Title} | ${Constants.companyName}`,
};
type Props = {
params: Promise<{ inquiryId: string }>;
};
export default async function FSP{StepName}Page({ params }: Props) {
const { inquiryId } = await params;
return <{StepName}Page inquiryId={inquiryId} />;
}
```
Header and content wrapper live in the feature page component. See [inquiry-process step-page guide](../inquiry-process/step-page.md).
### Template 5: Auth Page
```tsx
import { Metadata } from "next";
import { Constants } from "@/shared/utils/constants";
import { Panel } from "@finstreet/ui/components/base/Panel";
import { Headline } from "@finstreet/ui/components/base/Headline";
import { VStack } from "@styled-system/jsx";
import { getExtracted } from "next-intl/server";
export const metadata: Metadata = {
title: `Anmelden | ${Constants.companyName}`,
};
export default async function {PageName}Page() {
consRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.