Skip to content

Awais Ghaffar

Software engineer · Based in Malaysia

Building Streaming AI Editors with Vercel AI SDK and Structured Outputs

February 18, 2026


When building Lynt, our biggest architectural challenge wasn't generating resume text; it was delivering structured edits in real-time without locking the UI or causing flickering document states.

Traditional chatbot workflows return raw markdown strings. But in an interactive resume editor, the user needs deterministic updates across structured sections: experience bullet points, skills taxonomies, contact metadata, and ATS keyword alignments. If a model hallucinates malformed JSON or streams unstructured prose, the whole editor document breaks.


The problem with unconstrained streaming

Early prototypes used standard token streaming. The frontend would buffer text, attempt regex extraction on incoming tokens, and update document fields. This fails in three distinct ways:

  • Incomplete JSON parsing: Standard JSON parsers throw syntax errors until the final closing brace arrives. You either wait 8 seconds for the entire completion, or you build an unstable partial parser that breaks on nested arrays.
  • Lost edit history: Unconstrained generation frequently overwrites user-customized formatting, company names, or dates that the prompt never intended to touch.
  • Layout thrashing: Unstable token updates cause reflows across canvas templates and PDF previews.

Type safety at the boundary: streamObject & Zod

The solution was pairing the Vercel AI SDK (streamObject) with strict Zod schemas and edge-cached validation:

import { streamObject } from 'ai';
import { z } from 'zod';

const ResumeSectionSchema = z.object({
  bulletPoints: z.array(z.string()),
  atsScoreDelta: z.number(),
  matchedKeywords: z.array(z.string()),
  reasoning: z.string(),
});

By enforcing the schema at generation time, the model outputs valid partial JSON trees that can be consumed directly by React components as they arrive. Users see individual bullet points populate one by one with zero syntax corruption.


Optimistic UI & inline diffing

Rather than replacing text outright, we render incoming edits as an inline diff overlay. The candidate text streams into a virtual shadow node. The user can accept, decline, or edit the bullet point directly while the remaining sections continue to stream.

This transforms the interaction from an awkward waiting period into an active, collaborative editing flow.


Key Takeaways

  • Never stream unconstrained text into a structured database or state tree.
  • Colocate your validation schemas across backend route handlers and frontend state hooks.
  • Edge caching static ATS job requirements on Cloudflare Workers keeps prompt token counts minimal and response times under 400ms.

February 18, 2026