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 structured outputs with a Zod schema.
// services/openai/generateBranding.ts (pattern)
import openai from '@/config/openai'
import { z } from 'zod'
import { zodTextFormat } 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 response = await openai.responses.parse({
model: 'gpt-5-mini',
instructions:
'You are a brand identity expert. Keep responses concise and practical.',
input: `Industry: ${input.industry}\nKeywords: ${input.keywords}\nTone: ${input.tone}\n\nGenerate 5 brand names, 3 taglines, and 4 brand colors.`,
max_output_tokens: 400,
store: false,
text: {
format: zodTextFormat(BrandingSchema, 'branding'),
verbosity: 'low',
},
})
return response.output_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 —
zodTextFormatparses 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/openai/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.