AI Cloud Architecture Advisor
Use Zod and Claude to generate typed AWS architecture recommendations.
Introduction
30 Second Summary
When you ask an AI a question, the response comes back as freeform text. If you want to use that response in an app, you have to figure out how to extract the pieces you need. That is fragile and breaks constantly.
In this project, you will build an AI-powered architecture advisor using Claude, Next.js, and Zod. You will type a plain-English app description and get back a fully structured AWS architecture recommendation where every field is guaranteed to match a schema you define.
What You'll Build
You will type something like "a real-time chat app with 10k concurrent users" and instantly see a clean results panel showing the recommended compute service, database, storage layer, networking setup, security considerations, complexity rating, and Claude's reasoning for each choice.
By the end of this project, you'll have:
- A working form where you type any app description and get back a structured architecture recommendation in seconds.
- Claude's output guaranteed to match your Zod schema with no parsing errors or malformed responses.
- A rendered results UI with visual cards for each architecture layer (Compute, Database, Storage, Networking), a Security list, and a Complexity badge.
- Secret Mission: Generate a Mermaid.js architecture diagram showing how the recommended AWS services connect to each other.
Are there any prerequisites?
You will need Node.js (v20.9 or higher), an Anthropic API key, and VS Code installed. This guide covers all project setup from scratch, so no prior Next.js or Zod experience is needed.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Scaffold the Next.js Project and Install Dependencies
You know what you're building and why structured outputs matter. Now you need a working foundation before you can write any code.
In this step, you'll scaffold a Next.js project, install the Anthropic SDK and Zod, and configure your API key so the app can talk to Claude.
In this step, get ready to:
- Scaffold a Next.js project using create-next-app.
- Install the Anthropic SDK and Zod for structured AI outputs.
- Configure your API key and confirm the dev server runs.
Scaffold the Next.js project
You need a terminal open to run commands. Let's start by opening one.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Scaffold the project and move into it by running these commands:
npx create-next-app@latest arch-advisor --yes
cd arch-advisor
What does this command do?
The --yes flag skips all interactive prompts and applies the recommended defaults: TypeScript, Tailwind CSS, ESLint, App Router, and Turbopack.
It also creates a folder called arch-advisor on your Desktop, installs all base dependencies, and initializes a Git repository inside it.
- Confirm the folder was created by running:
ls
You should see files like package.json, next.config.ts, and a src folder in the output.
Seeing 'command not found' for npx?
This means Node.js is not installed or not on your PATH. Confirm Node.js is installed by running node --version. You need version 20.9.0 or higher for Next.js 16.3.
help me fix a 'command not found' error when running npx
Install the Anthropic SDK and Zod
The project needs two additional packages: the Anthropic SDK to call Claude, and Zod to define the schema that enforces structured outputs.
- Install both packages by running:
npm install @anthropic-ai/sdk zod
What are these packages?
The @anthropic-ai/sdk package is the official TypeScript SDK for calling Claude. It includes helpers like zodOutputFormat() that convert your Zod schemas into the format Claude expects.
Zod is a schema validation library. You'll use it to define exactly what shape Claude's responses must take.
You should see output confirming the packages were added to your package.json.
Install failing with permission errors?
Make sure you're inside the arch-advisor folder. Run pwd to check your current directory. If you see a different path, run cd ~/Desktop/arch-advisor first.
help me troubleshoot npm install errors in my Next.js project
Create the environment file and start the dev server
The Anthropic SDK automatically reads your API key from an environment variable called ANTHROPIC_API_KEY. Next.js loads variables from a .env.local file into your server-side code at runtime.
- Open VS Code in the project folder. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the arch-advisor folder, and click Open.
- Create a new file called .env.local in the root of the arch-advisor folder by pressing Cmd+N (macOS) or Ctrl+N (Windows) to create a new file.
- Paste the following into the file:
ANTHROPIC_API_KEY=your-api-key-here
- Replace your-api-key-here with your actual Anthropic API key.
- Save the file as .env.local by pressing Cmd+S (macOS) or Ctrl+S (Windows). When prompted for a filename, type .env.local and save it in the root of the arch-advisor folder.
Why .env.local?
Next.js automatically loads .env.local into your server environment. The ANTHROPIC_API_KEY variable is only accessible in server-side code (like API routes), never exposed to the browser. The file is also gitignored by default, keeping your key out of version control.
Now let's confirm everything is wired up by starting the development server.
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, same on macOS and Windows).
- Start the development server by running:
npm run dev
Before you check the browser, what do you think you'll see at the URL? A blank page, an error, or something else?
- Open your browser and navigate to http://localhost:3000.
You should see the default Next.js starter page with the Vercel logo and links to documentation. This confirms your project is running.
Page not loading at localhost:3000?
Check your terminal for error messages. If you see a port conflict, another process is using port 3000. Stop it or Next.js will automatically suggest a different port in the terminal output.
If you see an error about missing modules, run npm install again to make sure all dependencies are present.
help me debug why my Next.js dev server is not loading
✔️ Awesome, I've got everything!
Great. Make sure you've saved your .env.local file and your dev server is still running in the terminal.
ⓧ I'd like to double check the full code
Your .env.local file should contain exactly this (with your own key replacing the placeholder):
ANTHROPIC_API_KEY=your-api-key-here
Your project is scaffolded, your dependencies are installed, and your API key is ready. Next up, you'll design the Zod schema that defines the exact shape of Claude's architecture recommendations.
Design the Architecture Schema and Build the API Route
Right now, if you asked Claude for architecture advice, you'd get a wall of unstructured prose. Parsing freeform text into UI components is fragile and breaks whenever Claude changes its phrasing.
The fix is to define a Zod schema that describes the exact shape of the response you need. Claude's structured outputs feature guarantees the response matches your schema with no parsing guesswork.
In this step, get ready to:
- Define a Zod schema with fields for compute, database, storage, networking, security, complexity, and reasoning.
- Build an API route handler that sends descriptions to Claude and receives typed responses.
- Test the endpoint with a real request and verify structured JSON output.
Define the architecture schema
The schema is the most important piece of this project. It tells Claude exactly what fields to return, what types they should be, and what values are allowed.
- Create the file src/app/api/recommend/route.ts by running this command from your arch-advisor directory:
mkdir -p src/app/api/recommend && touch src/app/api/recommend/route.ts
- Confirm the file exists by running:
ls src/app/api/recommend/
You should see route.ts in the output.
- Open src/app/api/recommend/route.ts in VS Code and paste the following schema code:
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { NextResponse } from "next/server";
// Define the exact shape Claude must return
const ArchitectureSchema = z.object({
compute: z.object({
service: z.string(),
reason: z.string(),
}),
database: z.object({
service: z.string(),
reason: z.string(),
}),
storage: z.object({
service: z.string(),
reason: z.string(),
}),
networking: z.object({
service: z.string(),
reason: z.string(),
}),
security: z.array(z.string()),
complexity: z.enum(["low", "medium", "high"]),
reasoning: z.string(),
});
// Export the TypeScript type inferred from the schema
export type ArchitectureRecommendation = z.infer<typeof ArchitectureSchema>;
What does this code do?
Each field in ArchitectureSchema defines one layer of the architecture recommendation. The compute, database, storage, and networking fields are each objects with a service name and a reason explaining the choice.
The security field is an array of strings for multiple considerations. The complexity field uses z.enum() to restrict values to exactly "low", "medium", or "high". Claude cannot return anything outside this set.
The z.infer line creates a TypeScript type from the schema automatically. This means your frontend gets full type safety without manually defining a separate interface.
Set up the route handler with input validation
The schema defines what Claude returns. Now you need the Next.js route handler that receives requests and validates them before calling Claude.
- In src/app/api/recommend/route.ts, add the following code below the export type line:
// Anthropic client reads ANTHROPIC_API_KEY from .env.local automatically
const client = new Anthropic();
export async function POST(request: Request) {
// Parse the incoming JSON body
const body = await request.json();
const { description } = body;
// Reject requests missing a description
if (!description || typeof description !== "string") {
return NextResponse.json(
{ error: "Please provide an app description" },
{ status: 400 }
);
}
}
What does this code do?
The new Anthropic() constructor automatically reads your ANTHROPIC_API_KEY from the environment. No need to pass the key explicitly.
The POST function is a Next.js App Router route handler. Any POST request to /api/recommend hits this function. The validation guard returns a 400 error immediately if the description is missing or not a string.
- Save route.ts.
- Test the validation by sending an empty body to the route. Run this in a new terminal:
curl -X POST http://localhost:3000/api/recommend -H "Content-Type: application/json" -d '{}'
You should see the validation error response:
{"error":"Please provide an app description"}
Seeing a 404 or a different error?
- Make sure your dev server is still running in the other terminal (npm run dev).
- Check that the file path is exactly src/app/api/recommend/route.ts. Next.js uses the folder structure to create routes.
- Confirm the file is saved. Unsaved changes are invisible to the dev server.
help me debug my Next.js API route returning a 404
Call Claude with structured output and return the result
The route accepts requests and rejects bad ones. Now you wire in the Anthropic API call that sends the description to Claude and enforces your schema on the response.
- In route.ts, find the closing } of the if block. Replace the final } that closes the POST function with the following code:
// Call Claude with the schema enforced via zodOutputFormat
try {
const response = await client.messages.parse({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system:
"You are an expert AWS solutions architect. Given an app description, recommend the best AWS architecture. Be specific about which AWS services to use and explain why each is the best choice for the requirements described.",
messages: [
{
role: "user",
content: `Recommend an AWS architecture for the following app: ${description}`,
},
],
output_config: { format: zodOutputFormat(ArchitectureSchema) },
});
// parsed_output is already typed as ArchitectureRecommendation
return NextResponse.json(response.parsed_output);
} catch (error) {
console.error("Claude API error:", error);
return NextResponse.json(
{ error: "Failed to generate architecture recommendation" },
{ status: 500 }
);
}
}
What does this code do?
The client.messages.parse() method is the structured outputs entry point. It works like client.messages.create() but adds schema validation and automatic parsing.
The zodOutputFormat(ArchitectureSchema) helper converts your Zod schema into the JSON Schema format Claude expects. It also adds additionalProperties: false automatically so Claude cannot sneak in extra fields.
The response.parsed_output is already typed as your ArchitectureRecommendation type. No JSON.parse() or manual extraction needed.
Why zodOutputFormat instead of raw JSON Schema?
You could write the JSON Schema by hand, but zodOutputFormat() handles all the conversion automatically. It strips unsupported constraints, adds the required additionalProperties: false at every level, and keeps your schema definition in one place. Change the Zod schema and the API constraint updates automatically.
- Save route.ts.
Before you test this, what do you think the response will look like? Will it be freeform text, or will it match the exact fields you defined in your schema?
- Test the full endpoint by running this curl command:
curl -X POST http://localhost:3000/api/recommend -H "Content-Type: application/json" -d '{"description": "a simple blog"}'
You should see a structured JSON response with every field from your schema populated. It will look something like this (the specific AWS service names will vary):
{
"compute": { "service": "AWS Lambda", "reason": "..." },
"database": { "service": "Amazon DynamoDB", "reason": "..." },
"storage": { "service": "Amazon S3", "reason": "..." },
"networking": { "service": "Amazon CloudFront", "reason": "..." },
"security": ["...", "..."],
"complexity": "low",
"reasoning": "..."
}
Every field matches your Zod schema exactly. No extra fields, no missing fields, no freeform prose.
Seeing a 500 error or empty response?
- Check that your .env.local file contains a valid ANTHROPIC_API_KEY. The SDK reads it automatically but returns a cryptic error if the key is invalid.
- If you see a timeout, Claude may take 10-15 seconds to respond the first time. Wait and try again.
- Make sure you restarted the dev server after creating .env.local. Next.js only reads environment files on startup.
help me debug a 500 error from my Claude structured outputs API route
✔️ Awesome, I've got everything!
Great. Double check you have saved src/app/api/recommend/route.ts and that your curl response includes all seven fields from the schema.
ⓧ I'd like to double check the full code
Your src/app/api/recommend/route.ts should contain, in order:
- Four import lines (Anthropic, z, zodOutputFormat, NextResponse).
- The ArchitectureSchema definition with compute, database, storage, networking (each with service and reason), security, complexity, and reasoning.
- The export type ArchitectureRecommendation line using z.infer.
- The const client = new Anthropic() line.
- The POST handler with input validation, client.messages.parse() using zodOutputFormat(ArchitectureSchema), and a try/catch returning response.parsed_output or a 500 error.
Scroll up and confirm each chunk is present in your file, in this order.
Your API route now takes plain English and returns a guaranteed-structured architecture recommendation. Next up, you will build the frontend form that lets users type descriptions and sends them to this endpoint.
Build the Input Form and Connect to the API
Your API route is live and ready to accept requests. The problem is there is no way for a user to actually send it a description.
In this step, you will build the frontend form that collects an app description, sends it to your /api/recommend route, and displays the structured response right on the page.
In this step, get ready to:
- Build a client component with a textarea and submit button.
- Wire up the form to POST to your API route and handle loading and error states.
- Display the raw JSON response on the page to confirm end-to-end connectivity.
Set up the client component with state
The default page.tsx from Next.js is a server component. You need a client component because the form uses browser-only features like useState and event handlers.
- Open src/app/page.tsx in VS Code.
- Delete all the existing contents of the file.
- Replace it with this code, which sets up the client component directive, imports, the type interface, and state hooks:
"use client";
import { useState } from "react";
// Define the shape of the architecture response
export interface ArchitectureRecommendation {
compute: { service: string; reason: string };
database: { service: string; reason: string };
storage: { service: string; reason: string };
networking: { service: string; reason: string };
security: string[];
complexity: "low" | "medium" | "high";
reasoning: string;
}
export default function Home() {
const [description, setDescription] = useState("");
const [result, setResult] = useState<ArchitectureRecommendation | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
What does this code do?
"use client" tells Next.js this component runs in the browser, not on the server. You need this because React hooks like useState only work client-side.
The ArchitectureRecommendation interface mirrors the Zod schema you defined in your API route. This gives TypeScript full type checking on the response data.
The four state variables track the user's input (description), the API response (result), whether a request is in flight (loading), and any error message (error).
Add the form submission handler
- Directly below your state hooks (still inside the Home function), add the submission handler:
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
setResult(null);
try {
// POST the description to our API route
const response = await fetch("/api/recommend", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ description }),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Request failed");
}
// Parse the typed JSON response
const data: ArchitectureRecommendation = await response.json();
setResult(data);
} catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong");
} finally {
setLoading(false);
}
}
What does this function do?
handleSubmit fires when the learner clicks the submit button. It resets the state, sends the description to /api/recommend as a POST request, and stores the parsed response in result.
If the API returns an error status, it extracts the error message and throws. The finally block always runs, setting loading back to false regardless of success or failure.
Build the form UI and display the raw response
- Below the handleSubmit function, add the component's return statement with the form and result display:
return (
<main style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}>
<h1>AI Cloud Architecture Advisor</h1>
<p>Describe the app you want to deploy, and Claude will recommend an AWS architecture.</p>
<form onSubmit={handleSubmit}>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="e.g. A real-time chat app with 10k concurrent users, image uploads, and push notifications"
rows={4}
style={{ width: "100%", padding: "0.75rem", fontSize: "1rem" }}
/>
<button
type="submit"
disabled={loading || !description.trim()}
style={{ marginTop: "1rem", padding: "0.75rem 1.5rem", fontSize: "1rem" }}
>
{loading ? "Analyzing..." : "Get Architecture Recommendation"}
</button>
</form>
{error && <p style={{ color: "red", marginTop: "1rem" }}>{error}</p>}
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</main>
);
}
What does this JSX do?
The form binds onSubmit to your handler, so pressing the button triggers the API call. The button is disabled while loading or if the textarea is empty.
The last two lines are conditional renders. If an error exists, it shows in red. If a result exists, JSON.stringify(result, null, 2) dumps the raw typed response as formatted JSON so you can confirm the data structure.
- Save src/app/page.tsx.
- Return to your browser at http://localhost:3000. You should see a heading that says "AI Cloud Architecture Advisor", a textarea, and a submit button.
Seeing a compile error?
Check that your file starts with "use client"; on the very first line. Without it, Next.js treats the file as a server component and rejects useState.
Make sure the closing } of the Home function is on the last line. A missing brace causes a syntax error.
help me fix a compile error in my page.tsx client component
Now test the full flow. Before you submit, think about what you expect to see. Will the response come back instantly, or will there be a delay while Claude processes your request?
- Type a todo app with user accounts into the textarea.
- Click Get Architecture Recommendation.
The button text changes to "Analyzing..." while Claude processes. After a few seconds, a block of formatted JSON appears below the form showing compute, database, storage, networking, security, complexity, and reasoning fields. Every field is populated with real AWS service recommendations.
Why is this significant?
That JSON is not freeform text that you have to parse. It is guaranteed to match your Zod schema exactly. Every field is present, every type is correct, and every enum value is valid. This is the power of structured outputs.
Seeing an error message in red?
If you see "Please provide an app description", make sure the textarea is not empty before clicking submit.
If you see "Failed to generate architecture recommendation", check that your .env.local file has a valid API key and that your dev server is still running in the terminal.
help me debug why my architecture advisor API call is failing
✔️ Awesome, I've got everything!
Great. Double check that you have saved src/app/page.tsx and that the JSON response rendered on the page after submitting.
ⓧ I'd like to double check the full code
Scroll up and confirm your src/app/page.tsx contains these three sections in order:
- The "use client" directive, useState import, the ArchitectureRecommendation interface, and the four useState hooks inside Home().
- The handleSubmit async function that POSTs to /api/recommend and stores the result in state.
- The return statement with the form (textarea + button), error display, and JSON.stringify(result, null, 2) output.
Your app now works end-to-end. You type a description, Claude returns a fully structured recommendation, and it shows up on the page. Next up, you will turn that raw JSON into a polished UI with visual cards for each architecture layer.
Render the Typed Architecture Results
Your API returns perfectly structured data, but right now it shows up as a wall of raw JSON. That defeats the purpose of structured outputs.
The real payoff of a typed response is that you can render each field directly into a clean, scannable UI. No parsing, no transformation, no guessing what shape the data might be. In this step, you will build a results panel that maps each architecture layer into its own visual card.
In this step, get ready to:
- Create a ResultsPanel component that renders architecture layers as a grid of cards.
- Add Security, Complexity, and Reasoning sections to the results display.
- Wire the component into your page and see typed data rendered visually.
Create the ResultsPanel component with a ServiceCard helper
Each architecture layer (Compute, Database, Storage, Networking) follows the same pattern: a title, a service name, and a reason. A reusable ServiceCard sub-component keeps things clean.
- Create a new folder and file at src/app/components/ResultsPanel.tsx by right-clicking the src/app folder in VS Code's Explorer sidebar, selecting New Folder, naming it components, then right-clicking that folder and selecting New File named ResultsPanel.tsx.
- Add the imports, type definitions, color map, and ServiceCard helper by pasting this code:
import type { ArchitectureRecommendation } from "../page";
interface Props {
recommendation: ArchitectureRecommendation;
}
const complexityColors: Record<string, string> = {
low: "#22c55e",
medium: "#eab308",
high: "#ef4444",
};
function ServiceCard({ title, service, reason }: { title: string; service: string; reason: string }) {
return (
<div style={{ border: "1px solid #e5e7eb", borderRadius: "8px", padding: "1rem" }}>
<h3 style={{ margin: "0 0 0.5rem", fontSize: "0.875rem", color: "#6b7280" }}>{title}</h3>
<p style={{ margin: "0 0 0.5rem", fontWeight: "bold", fontSize: "1.125rem" }}>{service}</p>
<p style={{ margin: 0, color: "#4b5563", fontSize: "0.875rem" }}>{reason}</p>
</div>
);
}
What does this code do?
The Props interface accepts the full typed recommendation from your API. The complexityColors map will drive color-coded badges later.
The ServiceCard function is a reusable card that takes a title (like "Compute"), the AWS service name, and the reason Claude chose it. Each card follows the same layout so the grid looks consistent.
Add the ResultsPanel component with grid and complexity badge
The main exported component arranges the four ServiceCards in a 2x2 grid and adds a header with a color-coded complexity badge.
- In ResultsPanel.tsx, add the following code directly below the ServiceCard function:
export function ResultsPanel({ recommendation }: Props) {
return (
<div style={{ marginTop: "2rem" }}>
<div style={{ display: "flex", alignItems: "center", gap: "1rem", marginBottom: "1.5rem" }}>
<h2 style={{ margin: 0 }}>Architecture Recommendation</h2>
<span
style={{
backgroundColor: complexityColors[recommendation.complexity],
color: "white",
padding: "0.25rem 0.75rem",
borderRadius: "9999px",
fontSize: "0.75rem",
fontWeight: "bold",
textTransform: "uppercase",
}}
>
{recommendation.complexity} complexity
</span>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1rem", marginBottom: "1.5rem" }}>
<ServiceCard title="Compute" service={recommendation.compute.service} reason={recommendation.compute.reason} />
<ServiceCard title="Database" service={recommendation.database.service} reason={recommendation.database.reason} />
<ServiceCard title="Storage" service={recommendation.storage.service} reason={recommendation.storage.reason} />
<ServiceCard title="Networking" service={recommendation.networking.service} reason={recommendation.networking.reason} />
</div>
What's happening here?
The header div uses flexbox to place the heading and a pill-shaped complexity badge side by side. The badge background color comes from the complexityColors map you defined earlier, so it automatically turns green, yellow, or red based on Claude's response.
The grid uses CSS Grid with two equal columns. Each ServiceCard receives the typed fields directly from the recommendation object. No parsing or transformation needed.
Add the Security and Reasoning sections
The recommendation also includes a list of security considerations and Claude's overall reasoning. These go below the grid, still inside the main return.
- In ResultsPanel.tsx, add the following code directly below the closing </div> of the grid (the line after the Networking ServiceCard):
<div style={{ border: "1px solid #e5e7eb", borderRadius: "8px", padding: "1rem", marginBottom: "1rem" }}>
<h3 style={{ margin: "0 0 0.5rem", fontSize: "0.875rem", color: "#6b7280" }}>Security Considerations</h3>
<ul style={{ margin: 0, paddingLeft: "1.25rem" }}>
{recommendation.security.map((item, i) => (
<li key={i} style={{ marginBottom: "0.25rem", fontSize: "0.875rem" }}>{item}</li>
))}
</ul>
</div>
<div style={{ border: "1px solid #e5e7eb", borderRadius: "8px", padding: "1rem" }}>
<h3 style={{ margin: "0 0 0.5rem", fontSize: "0.875rem", color: "#6b7280" }}>Reasoning</h3>
<p style={{ margin: 0, fontSize: "0.875rem", color: "#4b5563" }}>{recommendation.reasoning}</p>
</div>
</div>
);
}
What does this code do?
The Security section maps over the security array (which your Zod schema guarantees is an array of strings) and renders each item as a bullet point.
The Reasoning section displays Claude's overall explanation for the architecture choices. Both sections are styled as bordered cards matching the ServiceCards above.
- Save ResultsPanel.tsx.
✔️ Awesome, I've got everything!
Great. Confirm you have saved the file before moving on.
ⓧ I'd like to double check the full code
Your ResultsPanel.tsx should contain these sections in order:
- The import of ArchitectureRecommendation from ../page.
- The Props interface and complexityColors map.
- The ServiceCard function rendering a bordered card with title, service, and reason.
- The exported ResultsPanel function with the header (h2 + complexity badge), the 2x2 grid of ServiceCards, the Security bullet list, and the Reasoning paragraph.
Scroll up and compare each section against the code blocks above.
Replace the raw JSON display with the ResultsPanel
The component is ready. Now you will swap the raw JSON dump for the visual panel.
- In src/app/page.tsx, find this import line near the top:
import { useState } from "react";
- Add a new import directly below it so both lines look like this:
import { useState } from "react";
import { ResultsPanel } from "./components/ResultsPanel";
- Now scroll to the bottom of the file and find this line:
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
- Replace that entire line with:
{result && <ResultsPanel recommendation={result} />}
Why no transformation needed?
Because your Zod schema guarantees the response shape, the result state variable already has the exact ArchitectureRecommendation type. You pass it directly to ResultsPanel with no JSON parsing, no type assertions, and no conditional checks. This is the core benefit of structured outputs.
- Save page.tsx.
Before you test, predict what you will see. If you type an app description and click submit, what should appear instead of raw JSON?
- Switch to your browser at http://localhost:3000.
- Type a todo app with user accounts in the textarea.
- Click Get Architecture Recommendation.
After a few seconds, you should see the architecture rendered as distinct visual cards for Compute, Database, Storage, and Networking. Each card shows the AWS service name in bold and Claude's reason below it. A colored complexity badge appears next to the heading, a Security section lists bullet points, and a Reasoning section shows Claude's overall explanation.
Not seeing the results panel?
- Check that the import path in page.tsx is exactly ./components/ResultsPanel. A wrong path causes a build error visible in your terminal.
- Make sure your ResultsPanel function uses the export keyword. Without it, the import in page.tsx fails.
- If the page loads but nothing appears after clicking submit, open the browser console (Cmd+Option+J on macOS, Ctrl+Shift+J on Windows) and check for errors.
Still stuck? Help me debug why my ResultsPanel component is not rendering after submitting a description.
Your structured output is no longer hidden in raw JSON. Every field Claude returns maps directly to a visual element. Next up, you will take this even further in the Secret Mission by generating a visual architecture diagram from the same typed data.
Secret mission
Generate a Visual Architecture Diagram
Your architecture advisor gives great text recommendations. But what if you could also see a visual flowchart showing how the recommended AWS services connect? In this secret mission, you will install Mermaid.js, build a DiagramPanel component that constructs a flowchart from the structured output, and render it as an SVG below the results panel.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project runs entirely locally. The only external cost is Anthropic API calls, which cost approximately $0.01-0.02 per request using Claude Sonnet 4.5. You are only charged when you actively submit a description through the app.
Resources you used:
- The arch-advisor project directory on your Desktop (all source code, dependencies, and configuration).
- The Next.js dev server running at http://localhost:3000.
- Your Anthropic API key stored in .env.local.
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment with different app descriptions and schema designs.
- Your dev server stays running at http://localhost:3000.
- Try submitting different app descriptions to see how Claude adapts its architecture recommendations.
- Each request costs approximately $0.01-0.02 on your Anthropic account.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your project files stay on disk so you can resume later.
- Switch to the terminal where your dev server is running.
- Press Ctrl+C to stop the server.
- When you want to resume, open a terminal in the arch-advisor folder and run npm run dev again.
Delete - I don't want to use this again
Remove all project files from your machine. This is permanent.
- Switch to the terminal where your dev server is running and press Ctrl+C to stop it.
- Navigate to your Desktop and remove the project directory by running:
macOS
cd ~/Desktop
rm -rf arch-advisor
Windows
cd ~\Desktop
rmdir /s /q arch-advisor
- If you want to revoke your Anthropic API key, go to the API Keys page in your Anthropic console and delete the key you created for this project.
Nice Work!
Nice Work!
You just built an AI-powered architecture advisor that turns plain-English app descriptions into structured, type-safe AWS recommendations using Claude and Zod.
You've learned how to:
- Design a multi-field Zod schema with nested objects, arrays, and enums to enforce the exact shape of Claude's output.
- Use client.messages.parse() with zodOutputFormat() to get guaranteed type-safe structured outputs from Claude with no manual parsing.
- Render typed JSON directly in React components as visual service cards, security lists, and complexity badges without any transformation layer.
- Secret Mission: Generated and rendered a Mermaid.js architecture diagram from the structured output, showing how recommended AWS services connect.
Ready to quiz yourself?