Release candidate — 1.0.0-rc.1
StitchAPI

Next.js

Web-standard helpers for Next App Router route handlers — sseResponse streams a stitch as text/event-stream, and stitchErrorResponse maps a StitchError to a Response.

Next App Router route handlers are Web-standard: they take a Request and return a Response. So a stitch already runs in one directly — define a seam once and call it in the handler. @stitchapi/next adds the two bits you'd otherwise hand-roll on the Web platform: streaming a stitch as Server-Sent Events, and mapping a StitchError to a Response.

It's built on Web standards only (Response, ReadableStream, TextEncoder) — no next import — so the same helpers also work in Remix, SvelteKit endpoints, Bun, Deno, and Workers.

Example

npm install @stitchapi/next stitchapi

stitchapi is the only peer dependency.

A route handler

A non-streaming endpoint is the stitch plus the error helper — no per-route try/catch shape to invent:

// app/api/users/[id]/route.ts
import { getUser } from '@/lib/api';

import { isStitchError, stitchErrorResponse } from '@stitchapi/next';

export async function GET(
    _req: Request,
    { params }: { params: Promise<{ id: string }> },
) {
    const { id } = await params;
    try {
        return Response.json(await getUser({ params: { id } }));
    } catch (err) {
        if (isStitchError(err)) return stitchErrorResponse(err);
        throw err;
    }
}

stitchErrorResponse maps a StitchError to 502 by default — the safe choice that never leaks the upstream's 401/404 semantics. Pass { status: (e) => e.status ?? 502 } to propagate the upstream status instead.

Streaming with SSE

sseResponse streams a stitch's events as text/event-stream. Each delta becomes one frame; an error event ends the stream with a named event: error frame; the terminal result closes it:

// app/api/chat/route.ts
import { chat } from '@/lib/api';

import { sseResponse } from '@stitchapi/next';

export async function POST(request: Request) {
    const { prompt } = await request.json();
    return sseResponse(chat({ body: { prompt } }).stream(), {
        data: (c) => String(c), // pull text out of each chunk
        signal: request.signal, // abort the upstream if the client leaves
    });
}

Pass request.signal so a client disconnect aborts the upstream iterator rather than leaving it running.

On the client, pair a streaming route with useStitchStream (or the Vue / Svelte / Angular binding) to render tokens as they arrive.

See also

On this page