Back to blog
·6 min read

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:

  • A clear entry point (usually a Server Action or API route)
  • A system prompt or instruction set (configuration management)
  • Access to tools or data sources (structured, isolated access)
  • Error handling and retry logic (resilience patterns)
  • Logging and observability (audit trails for AI decisions)
  • Version management (prompt evolution without breaking code)
  • When you scatter these concerns across your app/ directory, you end up with:

  • System prompts defined inline in route handlers
  • Tool definitions duplicated across multiple files
  • No clear versioning when prompts change
  • Impossible to reason about what data an AI agent can access
  • Debugging becomes a treasure hunt through your entire codebase
  • 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:

  • Tools are isolated and testable independently
  • Agents are versioned as single units
  • System prompts are centralized and easy to update
  • Adding observability means instrumenting /lib/agents/observability.ts, not hunting through your codebase
  • New developers understand where AI code lives and how it's organized
  • You can version agents, roll back prompts, and A/B test workflows
  • 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