This example demonstrates how to integrate Free-AI Gateway with a Next.js 14+ (App Router) application.
It shows:
- Connecting Next.js Route Handlers (
app/api/chat/route.ts) to Free-AI Gateway's OpenAI-compatible endpoint (http://localhost:3000/v1/chat/completions). - Streaming responses via Server-Sent Events (SSE).
- Using the Vercel AI SDK (
aipackage) with standard OpenAI client configuration. - Native standard
fetchstreaming implementation without external dependencies.
-
Ensure your Free-AI Gateway is running locally on
http://localhost:3000:# In the free-ai-gateway root npm run build npm start -
Make sure you have at least one free provider key configured in
.env(e.g.GROQ_API_KEY,GOOGLE_AI_STUDIO_API_KEY, orOPENROUTER_API_KEY).
examples/nextjs-chat/
├── app/
│ ├── api/
│ │ ├── chat/
│ │ │ └── route.ts # Vercel AI SDK streaming route handler
│ │ └── chat-native/
│ │ └── route.ts # Native fetch SSE streaming route handler
│ ├── layout.tsx # Root layout
│ └── page.tsx # Interactive chat component
├── package.json
├── tsconfig.json
└── README.md
import { OpenAIStream, StreamingTextResponse } from 'ai';
import OpenAI from 'openai';
// Point the OpenAI client to Free-AI Gateway local endpoint
const openai = new OpenAI({
apiKey: process.env.FREE_AI_API_KEY || 'free-ai-gateway-local',
baseURL: process.env.FREE_AI_GATEWAY_URL || 'http://localhost:3000/v1',
});
export const runtime = 'nodejs';
export async function POST(req: Request) {
const { messages, model = 'groq/llama-3.3-70b-versatile' } = await req.json();
const response = await openai.chat.completions.create({
model,
stream: true,
messages,
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}If you prefer zero external dependencies, you can proxy the SSE stream directly:
export const runtime = 'nodejs';
export async function POST(req: Request) {
const { messages, model = 'groq/llama-3.3-70b-versatile' } = await req.json();
const gatewayUrl = process.env.FREE_AI_GATEWAY_URL || 'http://localhost:3000/v1';
const gatewayResponse = await fetch(`${gatewayUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.FREE_AI_API_KEY || 'local'}`,
},
body: JSON.stringify({
model,
messages,
stream: true,
}),
});
if (!gatewayResponse.ok) {
const errorText = await gatewayResponse.text();
return new Response(errorText, {
status: gatewayResponse.status,
headers: { 'Content-Type': 'application/json' },
});
}
// Forward the SSE stream directly to the client
return new Response(gatewayResponse.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function ChatPage() {
const [selectedModel, setSelectedModel] = useState('groq/llama-3.3-70b-versatile');
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
body: { model: selectedModel },
});
return (
<main className="max-w-2xl mx-auto p-4 flex flex-col h-screen">
<header className="py-4 border-b flex justify-between items-center">
<h1 className="text-xl font-bold">Free-AI Gateway Chat</h1>
<select
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
className="border rounded p-1 text-sm bg-background"
>
<option value="groq/llama-3.3-70b-versatile">Groq: Llama 3.3 70B</option>
<option value="google/gemini-2.0-flash">Google: Gemini 2.0 Flash</option>
<option value="openrouter/auto">OpenRouter: Auto Free</option>
<option value="sambanova/Meta-Llama-3.1-8B-Instruct">SambaNova: Llama 3.1 8B</option>
</select>
</header>
<div className="flex-1 overflow-y-auto py-4 space-y-4">
{messages.map((m) => (
<div
key={m.id}
className={`p-3 rounded-lg ${
m.role === 'user' ? 'bg-blue-600 text-white ml-auto max-w-[80%]' : 'bg-muted max-w-[80%]'
}`}
>
<p className="text-xs font-semibold uppercase opacity-75 mb-1">{m.role}</p>
<p className="whitespace-pre-wrap">{m.content}</p>
</div>
))}
{isLoading && <div className="text-sm text-muted-foreground animate-pulse">Thinking...</div>}
</div>
<form onSubmit={handleSubmit} className="py-4 border-t flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Ask anything (routed via Free-AI Gateway)..."
className="flex-1 border rounded p-2 text-sm"
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="bg-primary text-primary-foreground px-4 py-2 rounded text-sm disabled:opacity-50"
>
Send
</button>
</form>
</main>
);
}Create .env.local in your Next.js project:
FREE_AI_GATEWAY_URL=http://localhost:3000/v1
FREE_AI_API_KEY=free-ai-gateway-local