Next.js + AI Project Structure: The /agents Directory Pattern for Production LLM Integration
Building AI-powered features into Next.js apps requires more than just adding API calls. Learn the production-ready folder structure that keeps your codebase maintainable as you integrate Claude, Codex, and custom LLM workflows.
The Growing Problem: 70% of Projects Need AI, Few Know How to Structure It
If you've built a Next.js application in the past year, you've probably added AI somewhere: a chat interface, an autonomous workflow, content generation, or data analysis. The problem isn't integrating AI anymore - it's keeping your codebase sane once you do.
At Groovy Web and across the developer community, the pattern is clear: roughly 70% of new production projects now require some form of LLM integration. Yet most developers are still treating AI like a bolt-on feature, scattering API calls across route handlers, scattered in utility files, mixed into components. When your model changes, your prompt structure evolves, or you need to add observability and retries, you're digging through your entire codebase.
The result? Codebases that become increasingly difficult to maintain, debug, and iterate on. Developers using Claude Code or Cursor report the same frustration: without a clear structure for AI code, these tools generate scattered implementations that don't fit your project architecture.
This is a structural problem that requires a structural solution.
Why Your Current Structure Breaks with AI
The standard Next.js App Router puts everything in the app/ directory: routes, components, hooks, utilities. This works fine for traditional applications. But AI workflows are different.
Consider what an AI agent or LLM integration needs:
When you scatter these concerns across your app/ directory, you end up with:
This isn't just a code organization problem - it's a reliability problem. Production AI systems need isolation, versioning, and clear interfaces.
The /agents Directory Pattern: Structure for Production AI
The solution is a dedicated directory for all AI-related code: /app/agents/
Here's the production-ready structure:
```
/app
/agents
/lib
prompt-manager.ts
tool-registry.ts
observability.ts
/tools
fetch-user-data.ts
generate-report.ts
send-notification.ts
/workflows
onboarding-agent.ts
support-agent.ts
data-processor.ts
/schemas
agent-input.ts
agent-output.ts
index.ts
/api
/agents
/onboarding
route.ts
/support
route.ts
/components
(standard React components)
/hooks
(standard React hooks)
```
Each agent lives in /workflows/ and is self-contained. Tools live in /tools/ with clear interfaces. Shared AI utilities (prompt management, tool registry, logging) live in /lib/agents/.
How This Structure Works in Practice
Let's build a real example: a simple content generation agent that takes a topic and creates blog outlines.
First, define the schema for input and output. Create /app/agents/schemas/agent-input.ts:
```typescript
import { z } from 'zod';
export const ContentAgentInput = z.object({
topic: z.string(),
targetAudience: z.string(),
style: z.enum(['technical', 'narrative', 'how-to']),
});
export type ContentAgentInputType = z.infer<typeof ContentAgentInput>;
```
Define a tool. Create /app/agents/tools/fetch-topic-research.ts:
```typescript
export async function fetchTopicResearch(topic: string) {
const response = await fetch(`/api/search?q=${encodeURIComponent(topic)}`);
const data = await response.json();
return data.results;
}
```
Build the agent workflow. Create /app/agents/workflows/content-agent.ts:
```typescript
import Anthropic from '@anthropic-ai/sdk';
import { ContentAgentInput } from '../schemas/agent-input';
import { fetchTopicResearch } from '../tools/fetch-topic-research';
const client = new Anthropic();
const SYSTEM_PROMPT = `You are a technical content strategist. Your job is to create detailed, SEO-focused blog outlines. Return valid JSON only, no other text.`;
export async function contentAgent(input: ContentAgentInput) {
const research = await fetchTopicResearch(input.topic);
const message = await client.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages: [
{
role: 'user',
content: `Create a blog outline for "${input.topic}" targeting ${input.targetAudience} in ${input.style} style. Here's current research: ${JSON.stringify(research)}`,
},
],
});
return message.content[0].type === 'text' ? JSON.parse(message.content[0].text) : null;
}
```
Create a Server Action to call the agent safely. Create /app/actions/generate-content.ts:
```typescript
'use server';
import { contentAgent } from '@/app/agents/workflows/content-agent';
import { ContentAgentInput } from '@/app/agents/schemas/agent-input';
export async function generateContentOutline(input: ContentAgentInput) {
try {
const result = await contentAgent(input);
return { success: true, data: result };
} catch (error) {
console.error('Agent error:', error);
return { success: false, error: 'Failed to generate outline' };
}
}
```
Now your component is clean:
```typescript
'use client';
import { useState } from 'react';
import { generateContentOutline } from '@/app/actions/generate-content';
export function ContentGenerator() {
const [outline, setOutline] = useState(null);
const [loading, setLoading] = useState(false);
const handleGenerate = async () => {
setLoading(true);
const result = await generateContentOutline({
topic: 'Next.js performance optimization',
targetAudience: 'developers',
style: 'technical',
});
if (result.success) setOutline(result.data);
setLoading(false);
};
return (
<div>
<button onClick={handleGenerate} disabled={loading}>
{loading ? 'Generating...' : 'Generate Outline'}
</button>
{outline && <pre>{JSON.stringify(outline, null, 2)}</pre>}
</div>
);
}
```
Why This Structure Scales
This pattern works because:
When Claude Code or another AI assistant generates agent code, it lands in the right place with a clear interface. Your codebase stays maintainable.
Scaling Beyond Single Agents
As you add more agents (support workflows, data processing, autonomous tasks), this structure grows cleanly:
```
/app/agents/workflows/
content-agent.ts
support-agent.ts
billing-agent.ts
data-processor-agent.ts
```
Each agent is self-contained. They share tools when it makes sense. They leverage the same prompt manager and observability infrastructure.
This is the pattern that production teams use - and it's especially valuable when you're using AI coding assistants to generate agent implementations. A clear structure means better code generation and faster iteration.
Getting Started
Start small: pick one AI workflow in your application. Create the /agents/ directory structure above. Define clear input/output schemas. Move your LLM calls there. Notice how much easier debugging and iteration becomes.
Then expand. Add another agent. Build shared utilities. Instrument observability. Your codebase will reward you with clarity.
For teams moving fast with AI, having a scaffolded, pre-structured Next.js project with the /agents pattern already in place saves weeks of refactoring. Try the free discovery chat at zipbuild.dev to explore how to accelerate your AI-powered development.
Written by ZipBuild Team
Ready to build with structure?
Try the free discovery chat and see how ZipBuild architects your idea.
Start Building