Complete Guide: Building an AI-Powered Next.js Application with Mastra and Ollama Integration
A comprehensive guide to building a Next.js application with Mastra and Ollama integration for AI-powered task automation, featuring real-time streaming, agent workflows, and production-ready deployment strategies.
Daniel Kliewer
Author, Sovereign AI


Building an AI-Powered Next.js Application with Mastra and Ollama
1. Introduction
The world of AI is rapidly evolving, with agent-based systems emerging as powerful tools for task automation and complex problem-solving. In this comprehensive tutorial, we'll walk through building a sophisticated Next.js application that integrates Mastra (a production-ready AI agent framework) with Ollama (an open-source local LLM runner) to create an intelligent task automation system.
What is Mastra? Mastra is an enterprise-grade framework for creating autonomous AI agents with advanced reasoning capabilities, built-in workflow management, and production-ready features. It enables developers to build reliable, observable AI agents that can decompose complex tasks into manageable steps and execute them methodically.
What is Ollama? Ollama allows you to run large language models (LLMs) locally on your machine rather than relying on cloud APIs. This approach provides privacy benefits, reduces costs, and eliminates API latency issues—making it ideal for development and privacy-sensitive applications.
By the end of this tutorial, you'll have created a web application where users can submit goals like "Create a content calendar for social media" or "Analyze quarterly sales data," and watch as an AI agent systematically works through the problem, documenting its reasoning and producing high-quality results.
2. Setting Up the Project
2.1 Prerequisites
Before starting, ensure you have:
- Node.js 18+ installed
- Basic knowledge of React and Next.js
- Ollama installed (we'll cover this in detail)
- A Mastra account (we'll help you set this up)
2.2 Creating a Next.js Application
Let's begin by creating a fresh Next.js project:
bash1npx create-next-app@latest mastra-ollama-app2cd mastra-ollama-app
During the setup, select the following options:
- Would you like to use TypeScript? → Yes (for type safety)
- Would you like to use ESLint? → Yes
- Would you like to use Tailwind CSS? → Yes (for styling)
- Would you like to use the src/ directory? → Yes (for organization)
- Would you like to use App Router? → Yes (for modern routing)
- Would you like to customize the default import alias? → No
2.3 Installing Dependencies
Install the Mastra client library and other necessary packages:
bash1npm install @mastraai/client ollama-js dotenv react-markdown
2.4 Setting Up Ollama
- Visit Ollama's official website and download the installer for your operating system.
- Install Ollama following the on-screen instructions.
- Open a terminal and pull the Mistral model (a powerful open-source LLM):
bash1ollama pull mistral
This will download the model, which may take several minutes depending on your internet connection.
2.5 Setting Up Mastra
- Visit Mastra's website and create an account
- Generate an API key from your dashboard
- Create a
.env.localfile in your project root with:
MASTRA_API_KEY=your_api_key_here
2.6 Verifying Your Setup
Let's ensure Ollama is working correctly:
bash1ollama run mistral "What can you help me with today?"
You should see a coherent response from the model, confirming Ollama is properly installed.
3. Understanding the Frontend (React + Next.js)
Now, let's build a responsive, user-friendly interface for our agent application.
3.1 Creating the Home Page Component
Create or replace the file at src/app/page.tsx with:
tsx1"use client";2import { useState, useRef, useEffect } from "react";3import ReactMarkdown from "react-markdown";45export default function Home() {6 const [goal, setGoal] = useState<string>("");7 const [logs, setLogs] = useState<string[]>([]);8 const [isRunning, setIsRunning] = useState<boolean>(false);9 const [result, setResult] = useState<string>("");10 const logsEndRef = useRef<HTMLDivElement>(null);1112 // Auto-scroll to the bottom of logs13 useEffect(() => {14 if (logsEndRef.current) {15 logsEndRef.current.scrollIntoView({ behavior: "smooth" });16 }17 }, [logs]);1819 const handleRunAgent = async () => {20 if (!goal.trim() || isRunning) return;2122 setIsRunning(true);23 setLogs(["🤖 Initializing Mastra agent powered by Ollama..."]);24 setResult("");2526 try {27 const response = await fetch("/api/run-agent", {28 method: "POST",29 headers: { "Content-Type": "application/json" },30 body: JSON.stringify({ goal }),31 });3233 if (!response.ok) {34 const errorData = await response.json();35 throw new Error(errorData.error || "Failed to run agent");36 }3738 // Use streaming for real-time updates39 const reader = response.body?.getReader();40 const decoder = new TextDecoder();4142 if (reader) {43 while (true) {44 const { done, value } = await reader.read();45 if (done) break;4647 const text = decoder.decode(value);48 const data = JSON.parse(text);4950 if (data.type === "log") {51 setLogs(logs => [...logs, data.message]);52 } else if (data.type === "result") {53 setResult(data.content);54 }55 }56 }57 } catch (error: any) {58 setLogs(logs => [...logs, `❌ Error: ${error.message}`]);59 } finally {60 setIsRunning(false);61 setLogs(logs => [...logs, "✅ Agent execution completed"]);62 }63 };6465 return (66 <main className="flex min-h-screen flex-col items-center p-8 max-w-5xl mx-auto">67 <h1 className="text-4xl font-bold mb-3">AI Agent Workspace</h1>68 <h2 className="text-xl text-gray-600 mb-8">Powered by Mastra + Ollama</h2>6970 <div className="w-full space-y-8">71 {/* Goal Input Section */}72 <div className="bg-white p-6 rounded-lg shadow-md">73 <h3 className="text-lg font-semibold mb-3">What would you like the agent to accomplish?</h3>74 <div className="flex gap-3">75 <input76 type="text"77 placeholder="e.g., Create a marketing plan for a new product launch"78 value={goal}79 onChange={(e) => setGoal(e.target.value)}80 className="flex-1 p-3 border rounded-md text-gray-800 focus:ring-2 focus:ring-blue-500"81 disabled={isRunning}82 />83 <button84 onClick={handleRunAgent}85 disabled={isRunning || !goal.trim()}86 className={`px-6 py-3 rounded-md font-medium transition ${87 isRunning ?88 "bg-gray-300 text-gray-600" :89 "bg-blue-600 text-white hover:bg-blue-700"90 }`}91 >92 {isRunning ? "Working..." : "Run Agent"}93 </button>94 </div>95 </div>9697 {/* Agent Logs Section */}98 <div className="bg-gray-50 rounded-lg shadow-md">99 <div className="bg-gray-100 p-4 rounded-t-lg border-b">100 <h3 className="text-lg font-semibold">Agent Thinking Process</h3>101 </div>102 <div className="p-4 max-h-80 overflow-y-auto">103 {logs.length === 0 ? (104 <p className="text-gray-500 italic">Agent logs will appear here...</p>105 ) : (106 <div className="space-y-2">107 {logs.map((log, index) => (108 <div key={index} className="p-3 bg-white rounded border">109 {log}110 </div>111 ))}112 <div ref={logsEndRef} />113 </div>114 )}115 </div>116 </div>117118 {/* Result Section */}119 {result && (120 <div className="bg-white rounded-lg shadow-md">121 <div className="bg-green-100 p-4 rounded-t-lg border-b">122 <h3 className="text-lg font-semibold text-green-800">Agent Result</h3>123 </div>124 <div className="p-6 prose max-w-none">125 <ReactMarkdown>{result}</ReactMarkdown>126 </div>127 </div>128 )}129 </div>130 </main>131 );132}
3.2 Understanding the Frontend Components
The frontend is built with several key features:
-
State Management:
goal: Stores the user's input tasklogs: Maintains an array of execution logsisRunning: Tracks the agent's execution stateresult: Stores the final output from the agent
-
Streaming Response Handling:
- Uses the Fetch API with a reader/decoder to process streamed updates
- Separates log updates from final results
-
UI Components:
- A clean input section for submitting tasks
- A scrollable log window showing the agent's reasoning process
- A formatted result section using React Markdown for rich text display
-
User Experience Enhancements:
- Auto-scrolling logs to keep the latest updates visible
- Disabled inputs during processing
- Visual feedback for running state
4. Building the Backend (API Route with Mastra + Ollama)
Now, let's create the serverless API endpoint that will run our Mastra agent with Ollama.
4.1 Creating the API Route
Create a file at src/app/api/run-agent/route.ts:
typescript1import { NextResponse } from 'next/server';2import { MastraClient, AgentSession } from '@mastraai/client';3import { OllamaProvider } from '@mastraai/client/providers';45export async function POST(request: Request) {6 // Initialize the response encoder for streaming7 const encoder = new TextEncoder();8 const stream = new TransformStream();9 const writer = stream.writable.getWriter();1011 // Function to send updates to the client12 const sendUpdate = async (data: any) => {13 await writer.write(encoder.encode(JSON.stringify(data) + '\n'));14 };1516 // Process the request in the background while streaming updates17 const processRequest = async () => {18 try {19 // Parse the request body20 const { goal } = await request.json();2122 if (!goal || typeof goal !== 'string') {23 await sendUpdate({24 type: 'log',25 message: '❌ Error: Please provide a valid goal'26 });27 writer.close();28 return;29 }3031 // Initialize Mastra with Ollama provider32 const ollamaProvider = new OllamaProvider({33 model: 'mistral',34 baseUrl: 'http://localhost:11434', // Default Ollama URL35 });3637 const mastra = new MastraClient({38 apiKey: process.env.MASTRA_API_KEY,39 provider: ollamaProvider,40 });4142 // Create and configure the agent session43 const session: AgentSession = await mastra.createSession({44 goal: goal,45 maxSteps: 7, // Limit steps for reasonable response times46 streamUpdates: true, // Enable streaming47 });4849 // Log initialization50 await sendUpdate({51 type: 'log',52 message: `🧠 Analyzing task: "${goal}"`53 });5455 // Execute the agent with a callback for progress updates56 session.onStepComplete(async (step) => {57 await sendUpdate({58 type: 'log',59 message: `📝 Step ${step.number}: ${step.description}`60 });6162 if (step.reasoning) {63 await sendUpdate({64 type: 'log',65 message: `🤔 Reasoning: ${step.reasoning}`66 });67 }68 });6970 const result = await session.execute();7172 // Send the final result73 await sendUpdate({74 type: 'result',75 content: result.output76 });7778 // Close the stream79 writer.close();80 } catch (error: any) {81 console.error('Agent execution error:', error);82 await sendUpdate({83 type: 'log',84 message: `❌ Error: ${error.message || 'Unknown error occurred'}`85 });86 writer.close();87 }88 };8990 // Start processing in the background91 processRequest();9293 // Return the stream response immediately94 return new NextResponse(stream.readable, {95 headers: {96 'Content-Type': 'application/json',97 'Transfer-Encoding': 'chunked',98 },99 });100}
4.2 Understanding the Backend Architecture
Our API route implements several advanced features:
-
Streaming Response:
- Uses the Web Streams API to send real-time updates to the frontend
- Maintains a single connection instead of polling
-
Mastra Integration:
- Initializes the Mastra client with the Ollama provider
- Creates an agent session with the user's goal
- Configures step limits and streaming capabilities
-
Progress Tracking:
- Uses the
onStepCompletecallback to report each step's progress - Separates reasoning logs from final results
- Uses the
-
Error Handling:
- Robust error catching and reporting
- Ensures the stream is properly closed even on errors
5. Running and Testing the Application
Now let's run our application and test its capabilities:
5.1 Starting the Development Server
Ensure Ollama is running, then start your Next.js application:
bash1npm run dev
Open your browser to http://localhost:3000.
5.2 Testing with Different Goals
Try entering various goals to test the agent's capabilities:
Business Planning Examples:
- "Create a marketing strategy for a new fitness app"
- "Develop a 30-day content calendar for a tech startup"
- "Draft a project plan for website redesign"
Creative Tasks:
- "Write a short story about time travel with a twist ending"
- "Create a detailed character profile for a fantasy novel"
- "Develop three unique logo concepts for a sustainable fashion brand"
Analytical Problems:
- "Analyze the pros and cons of remote work for a small business"
- "Compare three different pricing strategies for a SaaS product"
- "Create a SWOT analysis for entering the electric vehicle market"
5.3 Sample Interaction
Here's an example of how the agent might process a goal to "Create a 7-day social media plan for a coffee shop":
Agent Logs:
text1🤖 Initializing Mastra agent powered by Ollama...2🧠 Analyzing task: "Create a 7-day social media plan for a coffee shop"3📝 Step 1: Define the target audience and social media platforms4🤔 Reasoning: Before creating content, I need to understand who we're targeting and which platforms would be most effective for a coffee shop. Typically, Instagram and Facebook work well for food/beverage businesses.5📝 Step 2: Establish content themes and post types6🤔 Reasoning: Coffee shops can benefit from diverse content including product highlights, behind-the-scenes, customer features, and educational content about coffee.7📝 Step 3: Create a content calendar for Monday through Wednesday8🤔 Reasoning: I'll start with the first half of the week, focusing on driving early-week traffic when coffee shops might be slower.9📝 Step 4: Create a content calendar for Thursday through Sunday10🤔 Reasoning: For the latter half of the week, I'll focus on weekend promotions and creating content that encourages longer visits and higher purchases.11📝 Step 5: Add engagement strategies and hashtag recommendations12🤔 Reasoning: Social media success requires engagement beyond just posting. I'll add strategies for responding to comments and effective hashtags.13📝 Step 6: Include measurement metrics and success indicators14🤔 Reasoning: The plan should include ways to track effectiveness so the coffee shop can refine their approach.15📝 Step 7: Finalize the 7-day plan with implementation tips16🤔 Reasoning: I'll compile everything into a cohesive plan and add practical tips for implementation.17✅ Agent execution completed
Agent Result: The final output would be a comprehensive, day-by-day social media plan formatted in Markdown, including specific post ideas, optimal posting times, hashtag recommendations, and engagement strategies.
6. Expanding the Project
Once you have the basic application working, consider these enhancements to create a more powerful agent system:
6.1 Adding Specialized Agent Tools
Extend your Mastra agent with specialized tools for different tasks:
typescript1const mastra = new MastraClient({2 apiKey: process.env.MASTRA_API_KEY,3 provider: ollamaProvider,4 tools: [5 {6 name: 'webSearch',7 description: 'Search the web for current information',8 async execute(query: string) {9 // Integration with a search API like Serper or SerpAPI10 const response = await fetch(`https://api.search.com?q=${encodeURIComponent(query)}`, {11 headers: { 'Authorization': `Bearer ${process.env.SEARCH_API_KEY}` }12 });13 return response.json();14 }15 },16 {17 name: 'imageGenerator',18 description: 'Generate an image based on a description',19 async execute(prompt: string) {20 // Integration with image generation API21 const response = await fetch('https://api.stability.ai/v1/generation', {22 method: 'POST',23 headers: { 'Authorization': `Bearer ${process.env.STABILITY_API_KEY}` },24 body: JSON.stringify({ prompt })25 });26 return response.json();27 }28 }29 ]30});
6.2 Implementing a Database for Conversation History
Add persistence to your application with a database:
typescript1// Using Prisma with PostgreSQL2import { PrismaClient } from '@prisma/client';3const prisma = new PrismaClient();45// In your API route6async function saveSession(userId: string, goal: string, result: string, logs: string[]) {7 await prisma.agentSession.create({8 data: {9 userId,10 goal,11 result,12 logs: JSON.stringify(logs),13 createdAt: new Date()14 }15 });16}1718// Then retrieve past sessions19async function getUserSessions(userId: string) {20 return await prisma.agentSession.findMany({21 where: { userId },22 orderBy: { createdAt: 'desc' }23 });24}
6.3 Implementing Multi-Agent Workflows
Create complex workflows with multiple specialized agents:
typescript1// Research agent gathers information2const researchAgent = await mastra.createSession({3 goal: "Research current coffee industry trends and customer preferences",4 agentType: "researcher"5});6const researchResult = await researchAgent.execute();78// Strategy agent creates a plan based on research9const strategyAgent = await mastra.createSession({10 goal: `Create a marketing strategy based on these industry insights: ${researchResult.output}`,11 agentType: "strategist",12 context: researchResult.output13});14const strategyResult = await strategyAgent.execute();
6.4 Adding Authentication and User Management
Implement authentication to enable personalized experiences:
typescript1// Using NextAuth.js2import NextAuth from 'next-auth';3import GoogleProvider from 'next-auth/providers/google';45export default NextAuth({6 providers: [7 GoogleProvider({8 clientId: process.env.GOOGLE_CLIENT_ID,9 clientSecret: process.env.GOOGLE_CLIENT_SECRET,10 }),11 ],12 callbacks: {13 async session({ session, token }) {14 // Add user data to the session15 session.userId = token.sub;16 return session;17 },18 },19});
6.5 Performance Optimization and Model Selection
Allow users to choose different models based on their needs:
typescript1// In your API route2const modelOptions = {3 'mistral': { temperature: 0.7, maxTokens: 2048 },4 'llama3': { temperature: 0.5, maxTokens: 4096 },5 'wizard': { temperature: 0.8, maxTokens: 2048 }6};78const selectedModel = req.body.model || 'mistral';9const modelConfig = modelOptions[selectedModel];1011const ollamaProvider = new OllamaProvider({12 model: selectedModel,13 baseUrl: 'http://localhost:11434',14 temperature: modelConfig.temperature,15 maxTokens: modelConfig.maxTokens16});
7. Conclusion
In this comprehensive tutorial, we've built a sophisticated Next.js application that leverages Mastra and Ollama to create an AI-powered task automation system. This combination offers several key advantages:
-
Local Privacy: By running models through Ollama, you maintain control of your data without sending it to external API services.
-
Cost Efficiency: Eliminate per-token or per-request charges by running inference locally.
-
Enterprise-Ready Architecture: Mastra provides a structured framework for building reliable agents with built-in workflow management and monitoring.
-
Flexibility: The system we've built can be easily extended with additional tools, models, and capabilities.
As AI agent technology continues to evolve, frameworks like Mastra provide the structure and reliability needed to build production-grade applications. Combined with the local inference capabilities of Ollama, you can create powerful, privacy-respecting AI systems that solve real-world problems.
Additional Resources
- Mastra Documentation
- Ollama GitHub Repository
- Next.js Documentation
- LangChain.js (complementary framework)
- Prompt Engineering Guide
By mastering these technologies, you're well-positioned to build the next generation of intelligent applications that blend the best of human creativity with AI capabilities.

Sovereign AI: Building Local-First Intelligent Systems
by Daniel Kliewer · Paperback · 72 pages
The hands-on guide to building AI that runs on your hardware, keeps your data private, and eliminates cloud dependence. Working code included.