# Next.js · Pages Router

> El mismo SDK en el Pages Router: `_app`, rutas de API y `getServerSideProps`.


```bash
npm install @andco/sdk @andco/sdk-react
```

## Provider en `_app`

```tsx title="pages/_app.tsx"
import { AndcoProvider } from "@andco/sdk-react";
import type { AndcoBrowserOptions } from "@andco/sdk/browser";
import type { AppProps } from "next/app";

const clientOptions: AndcoBrowserOptions = {
  clientId: process.env.NEXT_PUBLIC_ANDCO_CLIENT_ID!,
  redirectTo: process.env.NEXT_PUBLIC_APP_ORIGIN!,
  initialScopes: ["openid", "email", "profile"],
};

export default function App({ Component, pageProps }: AppProps) {
  return (
    <AndcoProvider clientOptions={clientOptions}>
      <Component {...pageProps} />
    </AndcoProvider>
  );
}
```

No hace falta `"use client"`: en el Pages Router todo es cliente. Tampoco hay que proteger la construcción, porque es inerte y no toca `window` hasta que alguien lee la sesión.

## Leer la sesión

```tsx title="pages/account.tsx"
import { useAndcoSession } from "@andco/sdk-react";

export default function Account() {
  const session = useAndcoSession();
  if (session === undefined) return <p>Cargando…</p>;
  return session ? <p>{session.user.email}</p> : <p>Sin sesión</p>;
}
```

## Autorización conducida por el servidor

```ts title="pages/api/andco/authorize.ts"
import type { NextApiRequest, NextApiResponse } from "next";
import { andco } from "~/lib/andco";
import { storeTransaction } from "~/lib/session-cookie";

export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
  const { data: request, error } = await andco.oauth.createAuthorizationRequest({
    redirectTo: `${process.env.APP_ORIGIN}/api/andco/callback`,
    scopes: ["openid", "email", "profile"],
  });
  if (error) return res.status(500).json({ error: error.code });

  storeTransaction(res, request);
  res.redirect(request.authorizationUrl.href);
}
```

```ts title="pages/api/andco/callback.ts"
import type { NextApiRequest, NextApiResponse } from "next";
import { andco } from "~/lib/andco";
import { readTransaction, writeSessionCookie } from "~/lib/session-cookie";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const request = readTransaction(req);
  if (!request) return res.status(400).json({ error: "missing transaction" });

  const { data: session, error } = await andco.oauth.exchangeCallback({
    callbackUrl: new URL(req.url!, process.env.APP_ORIGIN),
    request,
  });
  if (error) return res.status(400).json({ error: error.code });

  writeSessionCookie(res, session);
  res.redirect("/");
}
```

## Datos en `getServerSideProps`

`andco.with(session)` no renueva por sí solo — es una consulta directa al token de la sesión, igual que en el [flujo genérico del servidor](/docs/getting-started/server-authentication#llamar-a-la-api-por-cuenta-de-una-persona). Un ejemplo completo tiene que resolver la cookie, renovar cuando esté vencida y persistir la sesión renovada:

```ts title="lib/session-cookie.ts"
import type { NextApiRequest, NextApiResponse } from "next";
import type { AndcoAuthorizationRequest, AndcoSession } from "@andco/sdk";

const SESSION_COOKIE = "andco_session";
const TRANSACTION_COOKIE = "andco_tx";

function cookieHeader(name: string, value: string, maxAge: number): string {
  return `${name}=${encodeURIComponent(value)}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax; Secure`;
}

export function storeTransaction(res: NextApiResponse, request: AndcoAuthorizationRequest): void {
  const serialized = JSON.stringify({ ...request, authorizationUrl: request.authorizationUrl.href });
  res.setHeader("Set-Cookie", cookieHeader(TRANSACTION_COOKIE, serialized, 600));
}

export function readTransaction(req: NextApiRequest): AndcoAuthorizationRequest | null {
  const raw = req.cookies[TRANSACTION_COOKIE];
  if (!raw) return null;
  const parsed = JSON.parse(raw);
  return { ...parsed, authorizationUrl: new URL(parsed.authorizationUrl) };
}

export function sessionFromCookie(req: NextApiRequest): AndcoSession | null {
  const raw = req.cookies[SESSION_COOKIE];
  if (!raw) return null;
  try {
    return JSON.parse(raw) as AndcoSession;
  } catch {
    return null;
  }
}

export function writeSessionCookie(res: NextApiResponse, session: AndcoSession): void {
  const maxAge = Math.max(0, session.expiresAt - Math.floor(Date.now() / 1000));
  res.setHeader("Set-Cookie", cookieHeader(SESSION_COOKIE, JSON.stringify(session), maxAge));
}
```

No está cifrada: solo la sirve `HttpOnly` y `Secure`. Para un dato tan sensible como un refresh token, considera cifrarla o guardar la sesión en tu propio almacén y dejar en la cookie solo un identificador.

```tsx title="pages/accounts.tsx"
import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
import { andco } from "~/lib/andco";
import { sessionFromCookie, writeSessionCookie } from "~/lib/session-cookie";
import { isExpired } from "@andco/sdk";

export const getServerSideProps = (async ({ req, res }) => {
  let session = sessionFromCookie(req);
  if (!session) return { redirect: { destination: "/api/andco/authorize", permanent: false } };

  if (isExpired(session) && session.refreshToken) {
    const refreshed = await andco.oauth.refresh(session.refreshToken);
    if (refreshed.error) return { redirect: { destination: "/api/andco/authorize", permanent: false } };
    session = refreshed.data;
    writeSessionCookie(res, session);
  }

  const { data, error } = await andco.with(session).rest.http.GET("/accounts");
  return { props: { accounts: error ? [] : data.data } };
}) satisfies GetServerSideProps;

export default function Accounts({ accounts }: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <ul>
      {accounts.map((account) => (
        <li key={account.id}>{account.name}</li>
      ))}
    </ul>
  );
}
```

Los tipos de `accounts` vienen del contrato OpenAPI a través de `data.data`, no de una interfaz que escribas a mano; `InferGetServerSidePropsType` es lo que le pasa ese tipo al componente sin declararlo dos veces.

Nunca devuelvas el token como prop: viaja al navegador dentro del HTML. Devuelve los datos que la página necesita y nada más — aquí, `accounts`, no la sesión.

## Diferencias con el App Router

| | Pages Router | App Router |
| --- | --- | --- |
| Provider | `pages/_app.tsx` | Client Component desde el layout |
| Rutas de servidor | `pages/api/*` | `app/api/*/route.ts` |
| Datos del servidor | `getServerSideProps` | Server Components |
| Aislar el secreto | convención `lib/` | `import "server-only"` |

El SDK es el mismo; lo que cambia es dónde vive cada pieza.
