New Page Skill
Create a new page in the Next.js App Router structure with proper layout, SEO, and components.
Trigger
Use this skill when: user says "create page", "add page", "new page", or "add route for [something]"
Workflow
Step 1: Determine Page Type
Ask: What kind of page?
- •Marketing Page (public) →
app/[name]/page.tsx - •Dashboard Page (protected) →
app/(dashboard)/[name]/page.tsx - •Auth Page (public, auth layout) →
app/(auth)/[name]/page.tsx
Step 2: Create the Page File
Marketing Page Template:
tsx
import { generateMetadata } from "@/lib/seo/metadata";
import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
export const metadata = generateMetadata({
title: "[PAGE TITLE]",
description: "[PAGE DESCRIPTION]",
});
export default function [PageName]Page() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
<section className="py-20">
<div className="container mx-auto px-4">
{/* Page content */}
</div>
</section>
</main>
<Footer />
</div>
);
}
Dashboard Page Template:
tsx
import { generateMetadata } from "@/lib/seo/metadata";
export const metadata = generateMetadata({
title: "[PAGE TITLE]",
description: "[PAGE DESCRIPTION]",
noIndex: true, // Dashboard pages shouldn't be indexed
});
export default function [PageName]Page() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">[Page Title]</h1>
<p className="text-muted-foreground">[Page description]</p>
</div>
{/* Page content */}
</div>
);
}
Dynamic Page Template:
tsx
import { notFound } from "next/navigation";
import { generateMetadata as generateSEO } from "@/lib/seo/metadata";
import type { Metadata } from "next";
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
// Fetch data and generate metadata
return generateSEO({
title: "[DYNAMIC TITLE]",
description: "[DYNAMIC DESCRIPTION]",
});
}
export default async function [PageName]Page({ params }: PageProps) {
const { slug } = await params;
// Fetch data
// if (!data) notFound();
return (
<div>
{/* Page content */}
</div>
);
}
// Optional: generate static paths
export function generateStaticParams() {
return [
// { slug: 'example' },
];
}
Step 3: Add Navigation Link
Update navigation in relevant places:
- •
lib/config/site.ts- mainNav or footerNav - •
components/layout/header.tsx- if custom nav needed
Step 4: Add Loading State (Optional)
Create loading.tsx in the same directory:
tsx
import { PageHeaderSkeleton } from "@/components/skeletons";
export default function Loading() {
return <PageHeaderSkeleton />;
}
Step 5: Add Error Boundary (Optional)
Create error.tsx in the same directory:
tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center py-20">
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
Step 6: Write Tests
Create __tests__/app/[page-name]/page.test.tsx:
tsx
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import Page from "@/app/[page-name]/page";
describe("[PageName] Page", () => {
it("renders correctly", () => {
render(<Page />);
expect(screen.getByRole("heading")).toBeInTheDocument();
});
});
Checklist
- • Page file created with correct structure
- • Metadata/SEO configured
- • Navigation updated (if needed)
- • Loading state added (if data fetching)
- • Tests written
- • Build passes:
pnpm build