This guide creates a small but production-minded foundation for generative UI development. The example stack uses TypeScript, Next.js, React, a schema validator, and Playwright. The same boundaries apply if your preferred frontend is Vue, Svelte, Angular, or a web-components architecture.

1. Install the prerequisites

Install Git, a current supported Node.js LTS release, and an editor such as Visual Studio Code. Verify the tools:

git --version
node --version
npm --version

Use an LTS Node.js line for predictable dependency support. Never commit model-provider credentials to source control.

2. Bootstrap the application

npx create-next-app@latest generative-ui-lab   --typescript --eslint --app --src-dir
cd generative-ui-lab
npm install zod
npm install -D @playwright/test
npx playwright install

Create .env.local for secrets and confirm it is excluded by .gitignore.

3. Establish clear boundaries

src/
  app/
    api/generate-ui/route.ts
    page.tsx
  components/generative/
    registry.tsx
    ComparisonTable.tsx
    ConfirmationCard.tsx
  lib/
    ui-schema.ts
    policy.ts
    telemetry.ts

The API route may call a model, but the browser should receive validated data rather than arbitrary executable code. The component registry is the security and design-system boundary.

4. Define a UI schema

import { z } from "zod";

export const uiNodeSchema = z.discriminatedUnion("component", [
  z.object({
    component: z.literal("Message"),
    props: z.object({ text: z.string().max(4000) })
  }),
  z.object({
    component: z.literal("ConfirmationCard"),
    props: z.object({
      title: z.string().max(120),
      actionId: z.string().regex(/^[a-z0-9-]+$/)
    })
  })
]);

Keep schemas narrow. Limit string length, enumerations, nesting depth, URLs, row counts, and action identifiers. Validation is not only for type safety; it controls cost, layout stability, and attack surface.

5. Render through a registry

const registry = {
  Message,
  ConfirmationCard
} as const;

export function RenderNode({ node }: { node: UiNode }) {
  const Component = registry[node.component];
  return <Component {...node.props} />;
}

Do not evaluate generated JavaScript. Do not insert untrusted markup with dangerouslySetInnerHTML. Keep navigation targets and external resources on allowlists.

6. Add progressive rendering

Generative experiences benefit from streaming, but partial data must remain valid. Stream complete typed events such as status, node, and error, rather than incomplete JSON fragments. Use an accessible status region for progress messages:

<p role="status" aria-live="polite">
  {generationStatus}
</p>

7. Add quality gates

npm run lint
npm run build
npx playwright test

Your tests should cover schema rejection, keyboard navigation, focus restoration, loading and error states, action confirmation, mobile layouts, and model timeouts. Mock model output in deterministic UI tests.

8. Recommended daily workflow

  1. Develop against recorded or mocked model responses.
  2. Validate every response at the server boundary.
  3. Review generated interfaces using keyboard-only navigation.
  4. Log schema failures and unknown component requests.
  5. Run automated tests before enabling a new component in the registry.

Environment checklist

  • Supported Node.js LTS and locked dependencies
  • Typed UI protocol with runtime validation
  • Secrets stored outside the repository
  • Finite component and action registries
  • Unit, integration, accessibility, and browser tests
  • Telemetry without sensitive prompt or user-data leakage

References

Node.js downloads · Next.js installation · W3C status messages

Author note

Prepared for the Victor Pereira technical-writing series with editorial assistance from OpenAI GPT-5.6 Thinking.