Overview
aSaaSin's AI feature uses OpenAI server actions with structured JSON output — the model returns a typed object your UI renders directly. The Branding Generator is the included example, but the same pattern works for any generative feature.
Prompt anatomy
A good structured-output prompt has four parts:
- Role — tell the model what it is ("You are a brand naming expert").
- Context — the user's inputs, concisely summarized.
- Constraints — what you want and what to avoid.
- Output format — describe the exact JSON shape or use
response_formatwith a Zod schema.
// services/ai/generateBranding.ts (pattern)
import { openai } from '@/lib/openai'
import { z } from 'zod'
import { zodResponseFormat } from 'openai/helpers/zod'
const BrandingSchema = z.object({
names: z.array(z.string()).length(5),
taglines: z.array(z.string()).length(3),
colors: z.array(z.string()).length(4), // hex strings
})
export async function generateBranding(input: {
industry: string
keywords: string
tone: string
}) {
const completion = await openai.beta.chat.completions.parse({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: `You are a brand identity expert. Return only valid JSON.`,
},
{
role: 'user',
content: `Industry: ${input.industry}\nKeywords: ${input.keywords}\nTone: ${input.tone}\n\nGenerate 5 brand names, 3 taglines, and 4 brand colors.`,
},
],
response_format: zodResponseFormat(BrandingSchema, 'branding'),
})
return completion.choices[0].message.parsed
}Prompt tips for structured output
- Be specific about counts — "generate exactly 5 names" is clearer than "generate some names".
- Name the output fields in the prompt — mention
names,taglines,colorsso the model maps its response to your schema. - Keep the system prompt short — one sentence on role and one on format. Long system prompts dilute focus.
- Validate before rendering —
zodResponseFormatparses the response; if it throws, surface a user-friendly error rather than crashing. - One concern per action — branding names in one action, color palettes in another. Mixing concerns makes prompts harder to tune.
Extending the Branding Generator
The Branding Generator (app/dashboard/playground/branding/) is a self-contained example. To add a new AI feature:
- Define a Zod schema for the output in
types/schemas.ts. - Write a service function in
services/ai/that calls OpenAI and returns the parsed schema. - Create a server action in
app/actions.ts(or a feature-specific actions file) that validates input, calls the service, and returnsActionResponse. - Build a client component that collects inputs, calls the action with
useTransition, and renders the result with apply/reset controls.
Keep OPENAI_API_KEY server-only. Never import it in client components or expose it via NEXT_PUBLIC_ prefix.