AgentSkillsCN

New Page

新页面

SKILL.md

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