# Pages Router (Legacy)

Complete reference for Next.js Pages Router with data fetching methods, API routes, and navigation.

> **Note**: The Pages Router is the legacy routing system. New projects should use the App Router for better performance and features.

The Pages Router provides traditional file-system based routing with data fetching methods including getStaticProps, getServerSideProps, and API routes for full-stack development.

## Capabilities

### useRouter Hook

Pages Router navigation hook for programmatic routing and accessing route state. Must be used in Client Components.

```typescript { .api }
import { useRouter } from 'next/router';

/**
 * Returns the Pages Router instance
 * @returns NextRouter with navigation methods and route state
 */
function useRouter(): NextRouter;

interface NextRouter {
  /**
   * The current pathname including dynamic route parameters
   */
  pathname: string;

  /**
   * The query string parsed as an object, including dynamic route parameters
   */
  query: ParsedUrlQuery;

  /**
   * The actual path shown in the browser
   */
  asPath: string;

  /**
   * The base path configured in next.config.js
   */
  basePath: string;

  /**
   * The active locale
   */
  locale?: string;

  /**
   * All configured locales
   */
  locales?: string[];

  /**
   * The default locale
   */
  defaultLocale?: string;

  /**
   * Whether the router is ready and router fields are updated client-side
   */
  isReady: boolean;

  /**
   * Whether the application is in preview mode
   */
  isPreview: boolean;

  /**
   * Whether the current page is a fallback page
   */
  isFallback: boolean;

  /**
   * Navigate to a new route
   * @param url - The URL to navigate to
   * @param as - Optional URL to show in browser
   * @param options - Navigation options
   */
  push(url: Url, as?: Url, options?: TransitionOptions): Promise<boolean>;

  /**
   * Replace the current route without adding to history
   * @param url - The URL to navigate to
   * @param as - Optional URL to show in browser
   * @param options - Navigation options
   */
  replace(url: Url, as?: Url, options?: TransitionOptions): Promise<boolean>;

  /**
   * Reload the current route
   */
  reload(): void;

  /**
   * Navigate back in the browser history
   */
  back(): void;

  /**
   * Navigate forward in the browser history
   */
  forward(): void;

  /**
   * Prefetch a route for faster navigation
   * @param url - The URL to prefetch
   * @param asPath - Optional URL to show in browser
   * @param options - Prefetch options
   */
  prefetch(url: Url, asPath?: Url, options?: PrefetchOptions): Promise<void>;

  /**
   * Set a callback to execute before route changes
   * @param callback - Callback function receiving route state
   */
  beforePopState(callback: BeforePopStateCallback): void;

  /**
   * Router events for listening to navigation lifecycle
   */
  events: RouterEvents;
}

interface TransitionOptions {
  shallow?: boolean;
  locale?: string | false;
  scroll?: boolean;
}

interface PrefetchOptions {
  priority?: boolean;
  locale?: string | false;
}

type Url = string | UrlObject;

interface UrlObject {
  auth?: string | null;
  hash?: string | null;
  host?: string | null;
  hostname?: string | null;
  href?: string | null;
  pathname?: string | null;
  protocol?: string | null;
  search?: string | null;
  slashes?: boolean | null;
  port?: string | number | null;
  query?: ParsedUrlQuery | null;
}

interface ParsedUrlQuery {
  [key: string]: string | string[] | undefined;
}
```

**Usage Examples**:

```typescript
import { useRouter } from 'next/router';

export default function Page() {
  const router = useRouter();

  // Access route information
  console.log('Pathname:', router.pathname); // "/products/[id]"
  console.log('Query:', router.query); // { id: "123", search: "shoes" }
  console.log('As Path:', router.asPath); // "/products/123?search=shoes"

  // Navigate programmatically
  const handleNavigate = () => {
    router.push('/about');
    router.push({ pathname: '/products', query: { category: 'shoes' } });
    router.push('/products/123', '/products/shoes'); // URL masking
  };

  // Replace without history
  const handleReplace = () => {
    router.replace('/login');
  };

  // Shallow routing (same page, different query)
  const handleFilter = (filter: string) => {
    router.push(
      { pathname: router.pathname, query: { ...router.query, filter } },
      undefined,
      { shallow: true }
    );
  };

  // Check if router is ready
  if (!router.isReady) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <button onClick={handleNavigate}>Navigate</button>
      <p>Current ID: {router.query.id}</p>
    </div>
  );
}
```

### Router Events

Listen to navigation lifecycle events for loading states and analytics.

```typescript { .api }
interface RouterEvents {
  /**
   * Subscribe to router events
   * @param event - Event name
   * @param handler - Event handler function
   */
  on(event: string, handler: (...args: any[]) => void): void;

  /**
   * Unsubscribe from router events
   * @param event - Event name
   * @param handler - Event handler function
   */
  off(event: string, handler: (...args: any[]) => void): void;
}
```

**Available Events**:
- `routeChangeStart(url, { shallow })` - Route change started
- `routeChangeComplete(url, { shallow })` - Route change completed
- `routeChangeError(err, url, { shallow })` - Route change error
- `beforeHistoryChange(url, { shallow })` - Before browser history change
- `hashChangeStart(url, { shallow })` - Hash change started
- `hashChangeComplete(url, { shallow })` - Hash change completed

**Usage Examples**:

```typescript
import { useRouter } from 'next/router';
import { useEffect } from 'react';

export default function App({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    const handleStart = (url: string) => {
      console.log(`Loading: ${url}`);
    };

    const handleComplete = (url: string) => {
      console.log(`Loaded: ${url}`);
    };

    const handleError = (err: Error, url: string) => {
      console.error(`Error loading ${url}:`, err);
    };

    router.events.on('routeChangeStart', handleStart);
    router.events.on('routeChangeComplete', handleComplete);
    router.events.on('routeChangeError', handleError);

    return () => {
      router.events.off('routeChangeStart', handleStart);
      router.events.off('routeChangeComplete', handleComplete);
      router.events.off('routeChangeError', handleError);
    };
  }, [router]);

  return <Component {...pageProps} />;
}
```

### GetStaticProps

Static generation with data fetching at build time. Generates static HTML pages that can be cached by a CDN.

```typescript { .api }
import type { GetStaticProps, GetStaticPropsContext, GetStaticPropsResult } from 'next';

/**
 * Data fetching function for static generation
 * @param context - Context object with params, preview data, and locales
 * @returns Props and revalidation configuration
 */
type GetStaticProps<Props = any, Params = ParsedUrlQuery, Preview = any> = (
  context: GetStaticPropsContext<Params, Preview>
) => Promise<GetStaticPropsResult<Props>> | GetStaticPropsResult<Props>;

interface GetStaticPropsContext<Params = ParsedUrlQuery, Preview = any> {
  /**
   * Dynamic route parameters
   */
  params?: Params;

  /**
   * Preview mode data
   */
  preview?: boolean;
  previewData?: Preview;

  /**
   * Current locale
   */
  locale?: string;

  /**
   * All configured locales
   */
  locales?: string[];

  /**
   * Default locale
   */
  defaultLocale?: string;

  /**
   * Revalidation request (ISR)
   */
  revalidateReason?: 'on-demand' | 'stale';
}

type GetStaticPropsResult<Props> =
  | { props: Props; revalidate?: number | boolean; notFound?: never; redirect?: never }
  | { props?: never; revalidate?: number | boolean; notFound: true; redirect?: never }
  | { props?: never; revalidate?: number | boolean; notFound?: never; redirect: Redirect };

interface Redirect {
  destination: string;
  permanent: boolean;
  statusCode?: number;
  basePath?: boolean;
}
```

**Usage Examples**:

```typescript
import type { GetStaticProps } from 'next';

interface Post {
  id: string;
  title: string;
  content: string;
}

interface PageProps {
  post: Post;
}

// Basic usage
export const getStaticProps: GetStaticProps<PageProps> = async () => {
  const post = await fetchPost();

  return {
    props: {
      post,
    },
  };
};

// With revalidation (ISR)
export const getStaticProps: GetStaticProps<PageProps> = async () => {
  const post = await fetchPost();

  return {
    props: {
      post,
    },
    revalidate: 60, // Revalidate every 60 seconds
  };
};

// With dynamic params
export const getStaticProps: GetStaticProps<PageProps> = async (context) => {
  const { id } = context.params as { id: string };
  const post = await fetchPost(id);

  // Return 404 if post not found
  if (!post) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      post,
    },
  };
};

// With redirect
export const getStaticProps: GetStaticProps<PageProps> = async (context) => {
  const post = await fetchPost();

  if (post.draft && !context.preview) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    };
  }

  return {
    props: {
      post,
    },
  };
};

// With preview mode
export const getStaticProps: GetStaticProps<PageProps> = async (context) => {
  const post = await fetchPost({
    preview: context.preview,
    previewData: context.previewData,
  });

  return {
    props: {
      post,
    },
  };
};

export default function Post({ post }: PageProps) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}
```

### GetStaticPaths

Specify dynamic routes to pre-render at build time. Required for dynamic routes using getStaticProps.

```typescript { .api }
import type { GetStaticPaths, GetStaticPathsContext, GetStaticPathsResult } from 'next';

/**
 * Define which dynamic routes to pre-render at build time
 * @param context - Context object with locales
 * @returns Paths to pre-render and fallback behavior
 */
type GetStaticPaths<Params = ParsedUrlQuery> = (
  context: GetStaticPathsContext
) => Promise<GetStaticPathsResult<Params>> | GetStaticPathsResult<Params>;

interface GetStaticPathsContext {
  /**
   * All configured locales
   */
  locales?: string[];

  /**
   * Default locale
   */
  defaultLocale?: string;
}

interface GetStaticPathsResult<Params = ParsedUrlQuery> {
  /**
   * Array of paths to pre-render
   */
  paths: Array<string | { params: Params; locale?: string }>;

  /**
   * Fallback behavior for paths not in paths array
   * - false: 404 for non-generated paths
   * - true: Generate on first request, show loading
   * - 'blocking': Generate on first request, wait for HTML
   */
  fallback: boolean | 'blocking';
}
```

**Usage Examples**:

```typescript
import type { GetStaticPaths, GetStaticProps } from 'next';

// Basic usage
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await getAllPosts();

  const paths = posts.map((post) => ({
    params: { id: post.id },
  }));

  return {
    paths,
    fallback: false, // 404 for non-generated paths
  };
};

// With fallback: true
export const getStaticPaths: GetStaticPaths = async () => {
  // Only pre-render most popular posts
  const popularPosts = await getPopularPosts();

  const paths = popularPosts.map((post) => ({
    params: { id: post.id },
  }));

  return {
    paths,
    fallback: true, // Other posts generated on-demand
  };
};

export const getStaticProps: GetStaticProps = async (context) => {
  const { id } = context.params as { id: string };
  const post = await fetchPost(id);

  if (!post) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      post,
    },
    revalidate: 60,
  };
};

export default function Post({ post }) {
  const router = useRouter();

  // Show loading state when fallback: true
  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// With catch-all routes
// pages/docs/[...slug].tsx
export const getStaticPaths: GetStaticPaths = async () => {
  const docs = await getAllDocs();

  const paths = docs.map((doc) => ({
    params: { slug: doc.path.split('/') },
  }));

  return {
    paths,
    fallback: 'blocking',
  };
};

// With locales
export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
  const posts = await getAllPosts();

  const paths = posts.flatMap((post) =>
    locales?.map((locale) => ({
      params: { id: post.id },
      locale,
    })) || [{ params: { id: post.id } }]
  );

  return {
    paths,
    fallback: false,
  };
};
```

### GetServerSideProps

Server-side rendering with data fetching on each request. Useful for frequently changing data or authenticated content.

```typescript { .api }
import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';

/**
 * Data fetching function for server-side rendering
 * @param context - Context object with request, response, params, and query
 * @returns Props or redirect/notFound response
 */
type GetServerSideProps<Props = any, Params = ParsedUrlQuery, Preview = any> = (
  context: GetServerSidePropsContext<Params, Preview>
) => Promise<GetServerSidePropsResult<Props>>;

interface GetServerSidePropsContext<Params = ParsedUrlQuery, Preview = any> {
  /**
   * HTTP request object
   */
  req: IncomingMessage & {
    cookies: Partial<{ [key: string]: string }>;
  };

  /**
   * HTTP response object
   */
  res: ServerResponse;

  /**
   * Dynamic route parameters
   */
  params?: Params;

  /**
   * Query string parameters
   */
  query: ParsedUrlQuery;

  /**
   * Preview mode data
   */
  preview?: boolean;
  previewData?: Preview;

  /**
   * The resolved URL path
   */
  resolvedUrl: string;

  /**
   * Current locale
   */
  locale?: string;

  /**
   * All configured locales
   */
  locales?: string[];

  /**
   * Default locale
   */
  defaultLocale?: string;
}

type GetServerSidePropsResult<Props> =
  | { props: Props | Promise<Props>; notFound?: never; redirect?: never }
  | { props?: never; notFound: true; redirect?: never }
  | { props?: never; notFound?: never; redirect: Redirect };

interface Redirect {
  destination: string;
  permanent: boolean;
  statusCode?: number;
  basePath?: boolean;
}
```

**Usage Examples**:

```typescript
import type { GetServerSideProps } from 'next';

interface User {
  id: string;
  name: string;
  email: string;
}

interface PageProps {
  user: User;
}

// Basic usage
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
  const user = await fetchUser();

  return {
    props: {
      user,
    },
  };
};

// With authentication
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
  const session = await getSession(context.req, context.res);

  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

  const user = await fetchUser(session.userId);

  return {
    props: {
      user,
    },
  };
};

// With dynamic params and query
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
  const { id } = context.params as { id: string };
  const { tab } = context.query;

  const user = await fetchUser(id);

  if (!user) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      user,
      activeTab: tab || 'profile',
    },
  };
};

// With cookies
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
  const token = context.req.cookies.token;

  if (!token) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

  const user = await fetchUser(token);

  return {
    props: {
      user,
    },
  };
};

// Set response headers
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
  const user = await fetchUser();

  // Set cache headers
  context.res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  );

  return {
    props: {
      user,
    },
  };
};

export default function Profile({ user }: PageProps) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
```

### API Routes

Create API endpoints within your Next.js application using file-based routing in the pages/api directory.

```typescript { .api }
import type { NextApiRequest, NextApiResponse, NextApiHandler } from 'next';

/**
 * HTTP request object for API routes
 */
interface NextApiRequest extends IncomingMessage {
  /**
   * Query string parameters
   */
  query: ParsedUrlQuery;

  /**
   * Request cookies
   */
  cookies: Partial<{ [key: string]: string }>;

  /**
   * Parsed request body
   */
  body: any;

  /**
   * Environment variables
   */
  env: Record<string, string>;

  /**
   * Preview mode status
   */
  preview?: boolean;

  /**
   * Preview mode data
   */
  previewData?: any;
}

/**
 * HTTP response object for API routes
 */
interface NextApiResponse<Data = any> extends ServerResponse {
  /**
   * Set the response status code
   * @param statusCode - HTTP status code
   */
  status(statusCode: number): NextApiResponse<Data>;

  /**
   * Send a JSON response
   * @param body - Response data
   */
  json(body: Data): void;

  /**
   * Send any response
   * @param body - Response body
   */
  send(body: Data): void;

  /**
   * Redirect to another URL
   * @param statusOrUrl - Status code or URL
   * @param url - URL if first param is status
   */
  redirect(url: string): void;
  redirect(status: number, url: string): void;

  /**
   * Set the response headers
   * @param name - Header name
   * @param value - Header value
   */
  setHeader(name: string, value: string | number | string[]): this;

  /**
   * Revalidate a path (ISR)
   * @param urlPath - Path to revalidate
   */
  revalidate(urlPath: string): Promise<void>;

  /**
   * Set preview mode data
   * @param data - Preview data
   */
  setPreviewData(data: any, options?: {
    maxAge?: number;
    path?: string;
  }): NextApiResponse<Data>;

  /**
   * Clear preview mode
   */
  clearPreviewData(): NextApiResponse<Data>;
}

/**
 * API route handler function type
 * @param req - Request object
 * @param res - Response object
 */
type NextApiHandler<Data = any> = (
  req: NextApiRequest,
  res: NextApiResponse<Data>
) => void | Promise<void>;
```

**Usage Examples**:

```typescript
import type { NextApiRequest, NextApiResponse } from 'next';

// Basic API route
// pages/api/hello.ts
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.status(200).json({ message: 'Hello World' });
}

// With type-safe response
interface User {
  id: string;
  name: string;
}

type ResponseData = User | { error: string };

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  const user = await fetchUser();

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.status(200).json(user);
}

// Handle different HTTP methods
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    const users = await getUsers();
    return res.status(200).json(users);
  }

  if (req.method === 'POST') {
    const newUser = await createUser(req.body);
    return res.status(201).json(newUser);
  }

  if (req.method === 'PUT') {
    const updatedUser = await updateUser(req.body);
    return res.status(200).json(updatedUser);
  }

  if (req.method === 'DELETE') {
    await deleteUser(req.query.id as string);
    return res.status(204).end();
  }

  // Method not allowed
  res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
  res.status(405).end(`Method ${req.method} Not Allowed`);
}

// Dynamic API route with params
// pages/api/posts/[id].ts
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { id } = req.query;

  const post = await getPost(id as string);

  if (!post) {
    return res.status(404).json({ error: 'Post not found' });
  }

  res.status(200).json(post);
}

// With authentication
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const token = req.cookies.token;

  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const user = await verifyToken(token);

  if (!user) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  const data = await getProtectedData(user.id);
  res.status(200).json(data);
}

// Revalidate on-demand (ISR)
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('/posts/1');
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).send('Error revalidating');
  }
}

// Preview mode
// pages/api/preview.ts
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { secret, slug } = req.query;

  if (secret !== process.env.PREVIEW_SECRET) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  const post = await getPreviewPost(slug as string);

  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' });
  }

  res.setPreviewData({ postId: post.id });
  res.redirect(post.slug);
}

// pages/api/exit-preview.ts
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  res.clearPreviewData();
  res.redirect('/');
}
```

### PageConfig

Page-level configuration for API routes and pages to control runtime behavior.

```typescript { .api }
/**
 * Configuration options for pages and API routes
 */
interface PageConfig {
  /**
   * API route specific configuration
   */
  api?: {
    /**
     * Maximum request body size (default: 1mb)
     */
    bodyParser?: {
      sizeLimit?: string | number;
    } | false;

    /**
     * Maximum response body size in bytes (default: 4mb)
     */
    responseLimit?: string | number | false;

    /**
     * External resolver flag (for custom servers)
     */
    externalResolver?: boolean;
  };

  /**
   * Runtime environment: 'nodejs' or 'edge'
   */
  runtime?: 'nodejs' | 'edge';

  /**
   * Disable runtime JS in production
   */
  unstable_runtimeJS?: boolean;

  /**
   * Disable JS preload
   */
  unstable_JsPreload?: boolean;

  /**
   * Maximum execution duration in seconds
   */
  maxDuration?: number;
}
```

**Usage Examples**:

```typescript
import type { NextApiRequest, NextApiResponse } from 'next';

// Disable body parsing for file uploads
export const config: PageConfig = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Handle multipart form data manually
  const formData = await parseMultipartForm(req);
  res.status(200).json({ success: true });
}

// Increase body size limit
export const config: PageConfig = {
  api: {
    bodyParser: {
      sizeLimit: '10mb',
    },
  },
};

// Edge runtime
export const config: PageConfig = {
  runtime: 'edge',
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Runs on Edge Runtime
  return res.json({ message: 'Edge function' });
}

// Increase execution timeout (on supported plans)
export const config: PageConfig = {
  maxDuration: 60, // 60 seconds
};
```

### withRouter HOC

Higher-order component to inject the router object into any component.

```typescript { .api }
import { withRouter } from 'next/router';
import type { WithRouterProps } from 'next/router';

/**
 * HOC to inject router into component props
 * @param Component - Component to wrap with router
 * @returns Component with router prop
 */
function withRouter<P extends WithRouterProps>(
  Component: React.ComponentType<P>
): React.ComponentType<Omit<P, keyof WithRouterProps>>;

interface WithRouterProps {
  router: NextRouter;
}
```

**Usage Examples**:

```typescript
import { withRouter } from 'next/router';
import type { NextRouter } from 'next/router';

// Class component
class MyComponent extends React.Component<{ router: NextRouter }> {
  componentDidMount() {
    console.log('Current path:', this.props.router.pathname);
  }

  render() {
    return <div>Path: {this.props.router.pathname}</div>;
  }
}

export default withRouter(MyComponent);

// Functional component (use useRouter hook instead when possible)
function MyFunctionalComponent({ router }: { router: NextRouter }) {
  return (
    <div>
      <p>Current path: {router.pathname}</p>
      <button onClick={() => router.push('/about')}>
        Go to About
      </button>
    </div>
  );
}

export default withRouter(MyFunctionalComponent);
```

### Type Utilities

Helper types to infer props from data fetching functions.

```typescript { .api }
import type { InferGetStaticPropsType, InferGetServerSidePropsType } from 'next';

/**
 * Infer the props type from a GetStaticProps function
 */
type InferGetStaticPropsType<T extends (...args: any) => any> =
  T extends GetStaticProps<infer P> ? P : never;

/**
 * Infer the props type from a GetServerSideProps function
 */
type InferGetServerSidePropsType<T extends (...args: any) => any> =
  T extends GetServerSideProps<infer P> ? P : never;
```

**Usage Examples**:

```typescript
// Infer props from getStaticProps
export const getStaticProps = async () => {
  const posts = await getPosts();
  return {
    props: {
      posts,
      timestamp: Date.now(),
    },
  };
};

type PageProps = InferGetStaticPropsType<typeof getStaticProps>;
// PageProps = { posts: Post[]; timestamp: number }

export default function Page({ posts, timestamp }: PageProps) {
  return <div>{posts.length} posts</div>;
}

// Infer props from getServerSideProps
export const getServerSideProps = async (context) => {
  const user = await getUser(context.params.id);
  return {
    props: {
      user,
      isAdmin: user.role === 'admin',
    },
  };
};

type ProfileProps = InferGetServerSidePropsType<typeof getServerSideProps>;
// ProfileProps = { user: User; isAdmin: boolean }

export default function Profile({ user, isAdmin }: ProfileProps) {
  return <div>{user.name}</div>;
}
```

## Types

```typescript { .api }
interface NextRouter {
  pathname: string;
  query: ParsedUrlQuery;
  asPath: string;
  basePath: string;
  locale?: string;
  locales?: string[];
  defaultLocale?: string;
  isReady: boolean;
  isPreview: boolean;
  isFallback: boolean;
  push(url: Url, as?: Url, options?: TransitionOptions): Promise<boolean>;
  replace(url: Url, as?: Url, options?: TransitionOptions): Promise<boolean>;
  reload(): void;
  back(): void;
  forward(): void;
  prefetch(url: Url, asPath?: Url, options?: PrefetchOptions): Promise<void>;
  beforePopState(callback: BeforePopStateCallback): void;
  events: RouterEvents;
}

interface ParsedUrlQuery {
  [key: string]: string | string[] | undefined;
}

interface TransitionOptions {
  shallow?: boolean;
  locale?: string | false;
  scroll?: boolean;
}

interface PrefetchOptions {
  priority?: boolean;
  locale?: string | false;
}

type Url = string | UrlObject;

interface UrlObject {
  auth?: string | null;
  hash?: string | null;
  host?: string | null;
  hostname?: string | null;
  href?: string | null;
  pathname?: string | null;
  protocol?: string | null;
  search?: string | null;
  slashes?: boolean | null;
  port?: string | number | null;
  query?: ParsedUrlQuery | null;
}

interface Redirect {
  destination: string;
  permanent: boolean;
  statusCode?: number;
  basePath?: boolean;
}

type InferGetStaticPropsType<T extends (...args: any) => any> =
  T extends GetStaticProps<infer P> ? P : never;

type InferGetServerSidePropsType<T extends (...args: any) => any> =
  T extends GetServerSideProps<infer P> ? P : never;
```

## Important Notes

- Pages Router is the legacy routing system; App Router is recommended for new projects
- All Pages Router components are client components by default
- getStaticProps and getStaticPaths only run at build time (or during revalidation)
- getServerSideProps runs on every request
- API routes should not be called from getStaticProps or getServerSideProps (call data layer directly)
- Use ISR (Incremental Static Regeneration) with revalidate for dynamic content that doesn't change frequently
- API routes are not bundled in the client-side code
- API routes can use Node.js modules and don't increase client bundle size
