> ## Documentation Index
> Fetch the complete documentation index at: https://docs.markpdf.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming y trabajos asincrónicos

> Streaming en Route Handlers y manejo de 202 (auto-asíncrono) en Server Actions.

# Streaming y trabajos asincrónicos

## Transmisión al navegador

Un controlador de ruta puede reenviar la transmisión API directamente como `ReadableStream`, sin almacenar todo el documento en la memoria del servidor:

```ts app/api/convert/stream/route.ts theme={null}
import { getServerClient } from "@markpdf/nextjs";

export const runtime = "nodejs";

export async function POST(req: Request) {
  const client = getServerClient({ apiKey: process.env.MARKPDF_API_KEY! });
  const { url } = await req.json();

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      try {
        for await (const chunk of client.convertStream({ url })) {
          controller.enqueue(encoder.encode(chunk));
        }
      } finally {
        controller.close();
      }
    },
  });

  return new Response(stream, { headers: { "content-type": "text/markdown; charset=utf-8" } });
}
```

En el cliente, consuma la respuesta con `ReadableStream` nativo o una biblioteca de streaming de su elección:

```tsx theme={null}
async function streamConversion(url: string) {
  const res = await fetch("/api/convert/stream", {
    method: "POST",
    body: JSON.stringify({ url }),
    headers: { "content-type": "application/json" },
  });
  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  let full = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    full += decoder.decode(value, { stream: true });
  }
  return full;
}
```

<Note>
  Las acciones del servidor no admiten la transmisión de respuestas (son llamadas RPC serializadas, no llamadas `Response` sin formato). Para la transmisión real al cliente, utilice un controlador de ruta.
</Note>

## Trabajos por saturación (202) en Acciones del Servidor

Los ayudantes `convertFormData` y `convertUrlAction` heredan `autoPoll` de SDK de Node.js (habilitado de forma predeterminada): si el servidor responde `202`, la acción del servidor espera internamente la encuesta y devuelve el Markdown final una vez completado.

```ts app/actions.ts theme={null}
"use server";

import { convertFormData } from "@markpdf/nextjs";

export async function convertUpload(formData: FormData) {
  // If there were 202, this call takes longer — the Server Action blocks until "completed"
  return convertFormData(formData, {
    apiKey: process.env.MARKPDF_API_KEY!,
    mode: "fast",
  });
}
```

<Warning>
  Las acciones del servidor tienen su propio tiempo de espera de plataforma (varía según su proveedor de alojamiento). Si espera que las conversiones demoren varios minutos debido a la saturación, considere un controlador de ruta con sondeo manual del cliente en lugar de bloquear la acción del servidor.
</Warning>

### Encuesta manual del cliente

```ts app/api/jobs/[id]/route.ts theme={null}
import { getServerClient } from "@markpdf/nextjs";
import { NextResponse } from "next/server";

export async function GET(_req: Request, { params }: { params: { id: string } }) {
  const client = getServerClient({ apiKey: process.env.MARKPDF_API_KEY! });
  const status = await client.getJob(params.id);
  return NextResponse.json(status);
}
```

```tsx theme={null}
async function pollUntilDone(jobId: string): Promise<string> {
  while (true) {
    const res = await fetch(`/api/jobs/${jobId}`);
    const status = await res.json();
    if (status.status === "completed") return status.body;
    if (status.status === "failed") throw new Error(status.error);
    await new Promise((r) => setTimeout(r, 5000));
  }
}
```

<Tip>
  En condiciones de carga normales, no verá `202`; solo sucede cuando todos los backends están saturados. El sondeo automático de `convertFormData`/`convertUrlAction` ya lo cubre en la mayoría de los casos.
</Tip>
