| name | next-pages-router |
| description | Use when working with Next.js Pages Router (pages/ directory). Invoke for getStaticProps, getServerSideProps, getStaticPaths, API routes in pages/api, dynamic routing, or migrating from pages/ to app/ directory. |
| allowed-tools | Read, Grep, Glob |
Next.js Pages Router Expert
Purpose
Expert knowledge of Next.js Pages Router architecture. Covers getStaticProps, getServerSideProps, getStaticPaths, API routes, dynamic routing, and the pages/ directory structure (legacy but still supported).
When to Use
Invoke this skill when:
- Working with existing pages/ directory projects
- Using getStaticProps or getServerSideProps
- Creating API routes in pages/api/
- Implementing getStaticPaths for dynamic routes
- Migrating from Pages Router to App Router
- Using _app.tsx or _document.tsx
- Implementing custom error pages
- Debugging Pages Router issues
- Working with middleware in Pages Router
Documentation Available
Location: /Users/zach/Documents/cc-skills/docs/next/
Coverage (~142 files in pages/ directory):
Data Fetching:
- getStaticProps (SSG)
- getServerSideProps (SSR)
- getStaticPaths (dynamic SSG)
- Client-side fetching (SWR, React Query)
Routing:
- File-based routing
- Dynamic routes [id]
- Catch-all routes [...slug]
- Optional catch-all [[...slug]]
API Routes:
- pages/api/ directory
- Request/response handling
- Middleware
Special Files:
- _app.tsx (Custom App)
- _document.tsx (Custom Document)
- _error.tsx (Custom Error)
- 404.tsx (404 page)
- 500.tsx (500 page)
Related Skills
- next-app-router: For modern app/ directory
- next-data-rendering: For rendering strategies
- next-config-optimization: For build configuration
Best Practices
- Use getStaticProps for static pages - Best performance
- Use getServerSideProps sparingly - Slower than static
- Implement getStaticPaths for dynamic routes - Enable SSG
- Use ISR with revalidate - Balance static and fresh data
- Keep API routes lightweight - Or use external API
- Leverage SWR for client-side - Stale-while-revalidate pattern
- Consider migrating to App Router - For new features
- Use fallback: 'blocking' - For better UX in ISR
Examples
Example 1: Static Site Generation (SSG)
Question: "How do I use getStaticProps for SSG?"
Response:
// pages/products/index.tsx
import { GetStaticProps } from 'next';
interface Product {
id: string;
name: string;
price: number;
}
interface Props {
products: Product[];
}
export default function ProductsPage({ products }: Props) {
return (
<div>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
}
// This runs at build time
export const getStaticProps: GetStaticProps<Props> = async () => {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: {
products,
},
revalidate: 3600, // ISR: Revalidate every hour
};
};
With Error Handling:
export const getStaticProps: GetStaticProps<Props> = async () => {
try {
const res = await fetch('https://api.example.com/products');
if (!res.ok) {
return { notFound: true }; // Show 404 page
}
const products = await res.json();
return {
props: { products },
revalidate: 3600,
};
} catch (error) {
console.error('Failed to fetch products:', error);
return { notFound: true };
}
};
Redirect:
export const getStaticProps: GetStaticProps = async () => {
const isMaintenanceMode = process.env.MAINTENANCE_MODE === 'true';
if (isMaintenanceMode) {
return {
redirect: {
destination: '/maintenance',
permanent: false,
},
};
}
return { props: {} };
};
References:
- See:
docs/next/pages/building-your-application/data-fetching/get-static-props/
Example 2: Dynamic Routes with getStaticPaths
Question: "How do I create dynamic static pages?"
Response:
// pages/products/[id].tsx
import { GetStaticProps, GetStaticPaths } from 'next';
interface Product {
id: string;
name: string;
description: string;
price: number;
}
interface Props {
product: Product;
}
export default function ProductPage({ product }: Props) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}
// Generate paths at build time
export const getStaticPaths: GetStaticPaths = async () => {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
// Generate paths for all products
const paths = products.map((product: Product) => ({
params: { id: product.id },
}));
return {
paths,
fallback: 'blocking', // Generate other pages on-demand
// fallback: false, // 404 for unknown paths
// fallback: true, // Show loading state then generate
};
};
// Fetch data for specific product
export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
const res = await fetch(`https://api.example.com/products/${params?.id}`);
if (!res.ok) {
return { notFound: true };
}
const product = await res.json();
return {
props: { product },
revalidate: 3600, // ISR
};
};
Fallback Options:
false: Only pre-rendered paths, others 404true: Show loading state, then generate page'blocking': Wait for generation, no loading state (better SEO)
With Fallback True:
import { useRouter } from 'next/router';
export default function ProductPage({ product }: Props) {
const router = useRouter();
// Show loading state if fallback is rendering
if (router.isFallback) {
return <div>Loading...</div>;
}
return <div>{/* Product content */}</div>;
}
References:
- See:
docs/next/pages/building-your-application/data-fetching/get-static-paths/
Example 3: Server-Side Rendering (SSR)
Question: "How do I use getServerSideProps for SSR?"
Response:
// pages/dashboard.tsx
import { GetServerSideProps } from 'next';
interface Props {
user: User;
stats: Stats;
}
export default function DashboardPage({ user, stats }: Props) {
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Total Sales: ${stats.totalSales}</p>
</div>
);
}
// Runs on every request
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
// Access request/response
const { req, res, query, params } = context;
// Get cookies/headers
const token = req.cookies.token;
if (!token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
// Fetch user data
const user = await fetch('https://api.example.com/user', {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());
const stats = await fetch('https://api.example.com/stats', {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());
// Set cache headers
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
);
return {
props: {
user,
stats,
},
};
};
With Authentication:
import { getSession } from 'next-auth/react';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return {
props: { session },
};
};
References:
- See:
docs/next/pages/building-your-application/data-fetching/get-server-side-props/
Example 4: API Routes
Question: "How do I create API routes in pages/api/?"
Response:
// pages/api/products.ts
import type { NextApiRequest, NextApiResponse } from 'next';
interface Product {
id: string;
name: string;
price: number;
}
type Data = Product[] | { error: string };
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
// Handle different methods
if (req.method === 'GET') {
const products = await db.products.findMany();
return res.status(200).json(products);
}
if (req.method === 'POST') {
const { name, price } = req.body;
if (!name || !price) {
return res.status(400).json({ error: 'Name and price required' });
}
const product = await db.products.create({
data: { name, price },
});
return res.status(201).json([product]);
}
// Method not allowed
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).json({ error: `Method ${req.method} not allowed` });
}
// pages/api/products/[id].ts
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { id } = req.query;
if (req.method === 'GET') {
const product = await db.products.findUnique({
where: { id: id as string },
});
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
return res.status(200).json(product);
}
if (req.method === 'PUT') {
const product = await db.products.update({
where: { id: id as string },
data: req.body,
});
return res.status(200).json(product);
}
if (req.method === 'DELETE') {
await db.products.delete({
where: { id: id as string },
});
return res.status(204).end();
}
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end();
}
// With middleware
import { NextApiHandler } from 'next';
function withAuth(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Verify token
try {
const user = await verifyToken(token);
(req as any).user = user;
return handler(req, res);
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
}
export default withAuth(async (req, res) => {
const user = (req as any).user;
res.json({ message: `Hello ${user.name}` });
});
References:
- See:
docs/next/pages/building-your-application/routing/api-routes/
Example 5: Custom _app and _document
Question: "How do I customize the App and Document?"
Response:
// pages/_app.tsx (Custom App)
import type { AppProps } from 'next/app';
import { SessionProvider } from 'next-auth/react';
import Layout from '@/components/layout';
import '@/styles/globals.css';
export default function MyApp({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
);
}
// pages/_document.tsx (Custom Document)
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
{/* Custom fonts */}
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
rel="stylesheet"
/>
{/* Analytics */}
<script
async
src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
// pages/404.tsx (Custom 404)
export default function Custom404() {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
</div>
);
}
// pages/500.tsx (Custom 500)
export default function Custom500() {
return (
<div>
<h1>500 - Server Error</h1>
<p>Something went wrong on our end.</p>
</div>
);
}
// pages/_error.tsx (Custom Error)
import { NextPageContext } from 'next';
interface Props {
statusCode?: number;
}
function Error({ statusCode }: Props) {
return (
<div>
<h1>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</h1>
</div>
);
}
Error.getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
References:
- See:
docs/next/pages/building-your-application/routing/custom-app/ - See:
docs/next/pages/building-your-application/routing/custom-document/
Common Patterns
ISR with On-Demand Revalidation
// pages/api/revalidate.ts
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.query.secret !== process.env.REVALIDATE_SECRET) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.revalidate('/products');
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
Client-Side Fetching with SWR
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export default function ProfilePage() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
Dynamic Imports (Code Splitting)
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('@/components/heavy-component'), {
loading: () => <p>Loading...</p>,
ssr: false, // Disable SSR for this component
});
export default function Page() {
return <DynamicComponent />;
}
Search Helpers
# Find pages router docs
grep -r "getStaticProps\|getServerSideProps\|pages directory" /Users/zach/Documents/cc-skills/docs/next/
# Find API routes docs
grep -r "API routes\|pages/api" /Users/zach/Documents/cc-skills/docs/next/
# Find data fetching docs
grep -r "data fetching\|getStaticPaths" /Users/zach/Documents/cc-skills/docs/next/
# List pages router files
ls /Users/zach/Documents/cc-skills/docs/next/pages/
Common Errors
getInitialProps blocks SSG: Don't use with getStaticProps
- Solution: Use getStaticProps or getServerSideProps instead
getStaticPaths missing: Dynamic route without getStaticPaths
- Solution: Add getStaticPaths or use getServerSideProps
Props serialization error: Non-serializable data in props
- Solution: Convert to JSON-serializable format (dates → strings)
API route CORS issues: Missing CORS headers
- Solution: Add CORS middleware or headers
Migration to App Router
Pages Router → App Router:
getStaticProps→ Server Component data fetchinggetServerSideProps→ Server Component withdynamic = 'force-dynamic'getStaticPaths→generateStaticParamspages/api/→app/api/route.ts_app.tsx→app/layout.tsx- Client-side fetching → Server Components or Client Components
Performance Tips
- Prefer getStaticProps - Fastest, serves from CDN
- Use ISR for freshness - Balance static and dynamic
- Implement getStaticPaths carefully - Don't generate too many paths
- Use fallback: 'blocking' - Better UX than fallback: true
- Minimize getServerSideProps - Slower than static
- Leverage client-side SWR - For user-specific data
- Optimize API routes - Keep lightweight
Notes
- Documentation covers Next.js 12-13 (Pages Router)
- Pages Router is still supported in Next.js 14+
- App Router is recommended for new projects
- getStaticProps runs at build time (SSG)
- getServerSideProps runs on every request (SSR)
- API routes run server-side only
- File paths reference local documentation cache
- For latest updates, check https://nextjs.org/docs/pages