How to Structure AI Features in Next.js Without Triggering a Complete Rewrite
Most teams scatter AI logic across route handlers and utilities, then face rewrites when requirements change. Here's the production-tested directory structure that keeps AI features maintainable as your app scales.
You've decided to add AI to your Next.js app. Maybe it's a Claude integration, maybe it's calling OpenAI for summarization, or maybe you're building AI agents that interact with your database. You start throwing LLM API calls into a route handler. Then another feature needs an agent. Then you need versioning on your prompts. Then you realize your authentication middleware doesn't work for agent requests. Now you're six weeks in and looking at a partial rewrite.
This is the architectural blind spot that catches nearly every team building AI-enhanced SaaS in 2026.
The problem isn't that AI is hard to integrate. The problem is that most Next.js structure guides don't have a pattern for it. You get opinions on /app vs /pages, on whether to use API routes or Edge Functions, on server vs client components. But when it comes to organizing AI logic—where prompts live, how agents interact with your database, how you version and test them—most teams wing it.
Then when you need to change a prompt, add authentication to an agent, or run multiple agents in parallel, you're untangling logic spread across your codebase.
The Core Problem: AI Logic Is Different From Normal Code
Here's why AI code can't live in your standard utilities folder or sprinkled through route handlers:
AI logic has versioning requirements. A prompt that works today might need tweaking in a month. You don't want that change cascading through your entire codebase.
Agents often have complex state. They might call your database, fetch external APIs, maintain conversation history, and make decisions based on that data. That's fundamentally different from a utility function that transforms data synchronously.
Prompts and system messages need to be centralized. When you change how Claude should behave, you want to change it in one place, not hunt through twelve files.
LLM calls are expensive. You want to log them, track tokens, rate limit them, and potentially cache responses. That belongs in a dedicated layer, not inline with your business logic.
The `/agents` directory pattern solves all of this by treating AI logic as first-class infrastructure, not an afterthought.
The /agents Directory Pattern: Structure That Prevents Rewrites
Here's what a production-ready AI-enhanced Next.js app looks like:
```
app/
api/
agents/
[agentId]/
route.ts
agents/
(this is where your actual agent code lives)
chat-agent/
index.ts
system-prompt.ts
tools.ts
summarizer-agent/
index.ts
system-prompt.ts
invoice-agent/
index.ts
system-prompt.ts
types.ts
middleware.ts
utils.ts
components/
lib/
utils/
```
Each agent is self-contained. The chat-agent folder has everything it needs: its initialization logic, its system prompt, its tools. When you need to change how the chat agent works, you're editing one folder, not hunting through your codebase.
Here's what the actual agent code looks like:
```
// app/agents/chat-agent/index.ts
import Anthropic from "@anthropic-ai/sdk";
import { systemPrompt } from "./system-prompt";
import { getAvailableTools, processToolCall } from "./tools";
export async function runChatAgent(
userMessage: string,
userId: string,
conversationHistory: any[]
) {
const client = new Anthropic();
const messages = [
...conversationHistory,
{ role: "user", content: userMessage },
];
const response = await client.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
system: systemPrompt,
tools: getAvailableTools(),
messages,
});
// Handle tool use
if (response.stop_reason === "tool_use") {
const toolUseBlocks = response.content.filter(
(block) => block.type === "tool_use"
);
for (const toolUse of toolUseBlocks) {
const result = await processToolCall(
toolUse.name,
toolUse.input,
userId
);
}
}
return response;
}
```
And the system prompt lives in its own file:
```
// app/agents/chat-agent/system-prompt.ts
export const systemPrompt = `You are a helpful customer support agent. You have access to tools to look up user information, process refunds, and escalate to human support.
Be concise. Always verify user identity before taking actions. When uncertain, ask clarifying questions.`;
```
Why does this matter? When your product manager says "we need the agent to be more helpful and less verbose," you change one file. When you need to add authentication checks, you add middleware in one place. When you need to log tokens for billing, you do it in the agent runner, not in twelve different route handlers.
Connecting to Your API: The Route Handler Layer
Your API routes are thin wrappers around agents:
```
// app/api/agents/chat/route.ts
import { runChatAgent } from "@/app/agents/chat-agent";
import { authenticateRequest } from "@/lib/auth";
import { logAgentCall } from "@/lib/logging";
export async function POST(request: Request) {
const session = await authenticateRequest(request);
if (!session) return new Response("Unauthorized", { status: 401 });
const { message, conversationId } = await request.json();
logAgentCall("chat-agent", session.userId);
try {
const response = await runChatAgent(
message,
session.userId,
conversationId
);
return Response.json(response);
} catch (error) {
logAgentCall("chat-agent", session.userId, "error");
return new Response("Agent error", { status: 500 });
}
}
```
The route handler handles HTTP concerns: authentication, logging, error handling. The agent handles AI concerns: prompts, tools, Claude API calls. This separation keeps both layers simple.
Why This Prevents Rewrites
When you add your fifth agent, you don't rethink your structure. You create `/agents/fifth-agent/` with the same pattern. Authentication? It's already in middleware. Logging? Already set up. Database access? You've got the utilities.
When requirements change, you change agent files. When you need to version prompts, you version the prompt file. When you add caching, you add it to the agent runner.
The worst case scenario—where you're forced into a rewrite—happens when AI logic is tangled with your business logic. This pattern keeps them separate. You can refactor agents without touching your components. You can change your API routes without breaking agent logic.
The Token Management Piece
One more critical detail: wrap your agent calls in a token logger. You're calling Claude. That costs money. You need to know:
```
// app/agents/middleware.ts
export async function logTokenUsage(
agentName: string,
userId: string,
response: any
) {
const inputTokens = response.usage?.input_tokens || 0;
const outputTokens = response.usage?.output_tokens || 0;
await db.tokenUsage.create({
agentName,
userId,
inputTokens,
outputTokens,
costEstimate: (inputTokens * 0.003 + outputTokens * 0.015) / 1000,
timestamp: new Date(),
});
}
```
This becomes critical when you're running multiple agents or when clients are asking about their AI usage.
Bringing It Together With ZipBuild
If you're building a SaaS product that needs AI features, the architecture matters more than the framework. ZipBuild handles this by scaffolding the `/agents` directory pattern into your initial codebase, so you're not making architectural decisions from scratch. Your agents directory is set up correctly from day one, with logging, authentication, and prompt versioning built in.
But even without scaffolding tools, this pattern is simple enough to implement yourself. Start with one agent. Create the folder structure. Write the system prompt as a separate file. Build the route handler as a thin wrapper. Then add your second agent using the exact same pattern. You'll immediately see how clean this becomes.
The goal is to reach a point where adding AI features feels like adding any other feature—you know exactly where code belongs, how authentication works, where to log things, and how to test it. That's when you know your architecture is working.
Try the free discovery chat at zipbuild.dev to see how the /agents pattern fits into a full Next.js scaffold built for AI-enhanced SaaS.
Written by ZipBuild Team
Ready to build with structure?
Try the free discovery chat and see how ZipBuild architects your idea.
Start Building