Vercel AI SDK + Next.js 15 : streaming, tool calling et RAG en production
Le Vercel AI SDK 5 est devenu le standard pour intégrer l'IA dans Next.js 15. Il abstrait le streaming, le tool calling et la gestion multi-provider en une API unifiée — et vous fait gagner des semaines de plomberie. Ce guide vous montre comment passer d'un prototype qui « répond » à un SaaS IA qui stream en temps réel, appelle vos outils métier et s'appuie sur vos données via RAG, sans sacrifier la stabilité ni exploser la facture.
Ce que vous allez construire
- Une route
/api/chatavec streaming natif et failover Groq → OpenAI - Une UI React avec
useChatqui affiche chaque token dès son arrivée - Du tool calling pour interroger votre base, créer un ticket ou vérifier un stock
- Un RAG léger sans infra lourde : chunking + embeddings + retrieval avant génération
- Rate limiting, abort et cache pour passer en production sereinement
Pourquoi le Vercel AI SDK plutôt qu'un fetch direct ?
Appeler l'API Groq ou OpenAI en fetch fonctionne pour une démo. En production, les problèmes arrivent vite : le streaming SSE nécessite un parser robuste, les erreurs réseau doivent déclencher un fallback, les outils (tools) exigent un schéma JSON strict, et chaque provider a ses subtilités de format. Le Vercel AI SDK uniformise tout cela.
Concrètement, le SDK vous offre trois briques : le transport streaming (gère les chunks, les erreurs et l'annulation), l'abstraction provider (même appel pour Groq, OpenAI, Anthropic, Google) et les helpers UI (useChat, useCompletion). Résultat : vous changez de modèle en une ligne, vous ajoutez un outil sans réécrire votre route, et votre interface reste fluide même sur une connexion mobile lente. Pour un SaaS qui vend de l'IA, cette vitesse perçue est directement liée à la conversion.
Autre avantage souvent ignoré : le SDK est pensé pour Next.js 15. Il fonctionne aussi bien en Route Handler Node.js qu'en Edge Runtime, expose des helpers pour les Server Actions et s'intègre avec le cache Next.js. Vous évitez ainsi de dupliquer la logique entre « code prototype » et « code prod ». Si vous partez de zéro, nos templates SaaS Next.js 15 incluent déjà cette architecture câblée avec authentification, billing Stripe et observabilité.
Étape 1 : installation et configuration
Installez le SDK et au moins deux providers pour le failover. Gardez Groq pour la latence et OpenAI en secours.
npm install ai @ai-sdk/openai @ai-sdk/groq zod
# .env.local
GROQ_API_KEY=gsk_xxx
OPENAI_API_KEY=sk-xxxLe fichier ai/providers.ts centralise vos clients. Cette centralisation vous permet de changer le modèle par défaut sans toucher aux routes, et de logger chaque appel au même endroit.
// lib/ai/providers.ts
import { createGroq } from '@ai-sdk/groq'
import { createOpenAI } from '@ai-sdk/openai'
export const groq = createGroq({ apiKey: process.env.GROQ_API_KEY! })
export const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })
export const models = {
fast: groq('llama-3.3-70b-versatile'),
balanced: groq('llama-3.1-8b-instant'),
fallback: openai('gpt-4o-mini'),
}Astuce coûts : déclarez un modèle « fast » pour les requêtes courtes (chat) et un modèle « balanced » pour les tâches longues. Vous divisez souvent la facture par deux sans dégrader l'expérience, car 80 % des interactions tiennent en moins de 300 tokens. Notre guide sur l'optimisation des coûts LLM en production détaille cette stratégie avec des chiffres réels par provider.
Étape 2 : le streaming temps réel avec useChat
Le streaming est l'attente perçue la plus critique. Un utilisateur qui voit les mots apparaître tolère 4 secondes ; sans streaming, il abandonne à 1,5 seconde. Le SDK rend le streaming trivial côté serveur et côté client.
// src/app/api/chat/route.ts
import { streamText } from 'ai'
import { models } from '@/lib/ai/providers'
export const maxDuration = 30
export async function POST(req: Request) {
const { messages } = await req.json()
// Garde-fou : limite l'historique
const history = messages.slice(-12)
const result = streamText({
model: models.fast,
system: 'Tu es un assistant SaaS qui répond en français, concis et utile.',
messages: history,
maxTokens: 800,
temperature: 0.7,
})
return result.toDataStreamResponse()
}Côté client, useChat gère l'état, le streaming et l'annulation. Pas degetReader() manuel, pas de parsing SSE artisanal.
// app/chat/page.tsx
'use client'
import { useChat } from 'ai/react'
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat({
api: '/api/chat',
})
return (
<div className="max-w-2xl mx-auto">
{messages.map(m => (
<div key={m.id} className={m.role === 'user' ? 'text-white' : 'text-indigo-200'}>
{m.content}
</div>
))}
<form onSubmit={handleSubmit} className="flex gap-2 mt-6">
<input value={input} onChange={handleInputChange} placeholder="Votre question..." className="flex-1 rounded-lg bg-white/5 border border-white/10 px-4 py-2" />
<button disabled={isLoading} className="rounded-lg bg-indigo-600 px-4 py-2 text-white disabled:opacity-50">
{isLoading ? '...' : 'Envoyer'}
</button>
{isLoading && <button type="button" onClick={() => stop()} className="text-sm text-indigo-400">Stop</button>}
</form>
</div>
)
}Le bouton Stop appelle stop() qui envoie un signalAbortSignal au serveur. Votre route s'interrompt proprement, vous économisez des tokens et l'utilisateur garde le contrôle — un détail UX qui réduit fortement la frustration.
Étape 3 : tool calling — quand le modèle appelle vos API
Le tool calling (ou function calling) permet au modèle de demander l'exécution d'une fonction que vous exposez : rechercher une commande, vérifier un stock, créer un ticket. C'est le pont entre le langage naturel et votre métier.
// src/app/api/chat/route.ts — avec tools
import { streamText, tool } from 'ai'
import { z } from 'zod'
import { models } from '@/lib/ai/providers'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: models.fast,
messages: messages.slice(-12),
tools: {
getOrder: tool({
description: 'Récupère une commande par ID',
parameters: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => {
// Remplacez par votre Prisma / API interne
const order = await db.order.findUnique({ where: { id: orderId } })
return order ?? { error: 'Commande introuvable' }
},
}),
createTicket: tool({
description: 'Crée un ticket support',
parameters: z.object({
subject: z.string(),
priority: z.enum(['low', 'medium', 'high']),
}),
execute: async ({ subject, priority }) => {
const ticket = await db.ticket.create({ data: { subject, priority } })
return { ticketId: ticket.id, status: 'ouvert' }
},
}),
},
maxSteps: 3, // autorise 3 allers-retours outil → modèle
})
return result.toDataStreamResponse()
}Deux règles d'or : validez chaque paramètre avec Zod (le modèle peut halluciner un champ) et limitez les étapes avec maxStepspour éviter les boucles infinies. Côté UI, le SDK expose toolInvocationsdans chaque message : affichez un état « Vérification de votre commande… » pendant l'exécution pour que l'utilisateur comprenne l'attente. Pour aller plus loin, lisez notre tutoriel sur la création d'un chatbot IA avec Next.js et Groq, qui détaille le streaming sans SDK.
Étape 4 : RAG léger sans usine à gaz
Le RAG (Retrieval-Augmented Generation) ancre les réponses dans vos données. Pas besoin de vecteur DB lourde au début : un fichier Markdown + embeddings suffit pour 90 % des SaaS early-stage.
- Chunking : découpez votre doc en blocs de 400–600 tokens avec 50 tokens d'overlap.
- Embeddings : générez les vecteurs une fois (OpenAI
text-embedding-3-smallà 0,02 $ / 1M tokens). - Retrieval : au moment de la requête, récupérez les 3–5 chunks les plus proches en similarité cosinus.
- Injection : concaténez ces chunks dans le prompt système avant d'appeler le modèle.
// Avant streamText : retrieval
const queryEmbedding = await embed(query) // votre fonction d'embedding
const topChunks = await findNearest(queryEmbedding, 4) // cosinus sur vos vecteurs
const context = topChunks.map(c => c.text).join('\n---\n')
const result = streamText({
model: models.fast,
system: `Tu réponds en français en t'appuyant sur ce contexte :
${context}`,
messages: history,
})Stockez les embeddings en JSON ou dans Postgres avec l'extension pgvectorsi vous avez déjà une base. Cette approche tient jusqu'à ~50 000 chunks avant de nécessiter un vrai vector store. Notre article RAG avec Next.js et Groq : système complet montre l'implémentation chunking → vector store → retrieval pas à pas, et RAG vs fine-tuning : comment décider vous aide à choisir la bonne architecture selon votre cas d'usage.
Failover, erreurs et garde-fous production
En production, un provider tombe. Sans failover, votre SaaS tombe avec lui. Le SDK facilite le fallback, mais vous devez l'orchestrer.
import { streamText } from 'ai'
import { models } from '@/lib/ai/providers'
async function streamWithFallback(messages: any[]) {
try {
return streamText({ model: models.fast, messages })
} catch (e) {
console.warn('Groq indisponible, fallback OpenAI', e)
return streamText({ model: models.fallback, messages })
}
}Ajoutez trois garde-fous systématiques : AbortSignal.timeout(15_000) sur chaque appel IA pour éviter les requêtes pendantes, un rate limit par IP et par utilisateur(100 req/min IP + quota par plan), et un circuit breaker qui ouvre après 5 échecs consécutifs et retente après 30 secondes. Ce dernier pattern est détaillé dans notre guide circuit breaker pour Next.js. Enfin, loguez chaque erreur avec l'ID de requête : sans corrélation, vous déboguerez à l'aveugle.
Checklist prod avant le go-live
- Timeout + AbortSignal sur tous les appels IA
- Validation Zod stricte sur tous les tools
- Rate limiting Edge (middleware) + DB pour les quotas payants
- Circuit breaker avec métriques (taux d'échec / latence p95)
- Monitoring : loggez latence, tokens, provider utilisé — voir monitoring IA en production
Optimiser les coûts : cache et déduplication
Le poste « tokens » est le plus volatile. Deux leviers simples divisent la facture sans toucher à la qualité. D'abord, mettez en cache les réponses aux prompts fréquents (FAQ, onboarding) avec un TTL de 1 heure via Vercel KV ou Redis. Ensuite, dédupliquez côté client : désactivez le bouton pendant le streaming et debouncez l'input à 300 ms pour éviter les doubles envois.
Pour les SaaS à fort volume, ajoutez un cache sémantique : si une nouvelle question est à > 92 % de similarité cosinus d'une question déjà répondue, servez la réponse cachée. Consultez notre comparatif coût réel des API IA en 2026 et coûts LLM en production pour chiffrer précisément ces optimisations selon votre provider.
Déploiement sur Vercel en 2 minutes
Le déploiement ne nécessite aucune config exotique. Poussez votre code, déclarez vos variables d'environnement dans le dashboard Vercel (ou via vercel env add) et déployez.
# Variables Vercel (Production)
GROQ_API_KEY=gsk_xxx
OPENAI_API_KEY=sk-xxx
# Déploiement
vercel --prodChoisissez le runtime selon votre cas : Edge Runtime pour la latence minimale (streaming au plus proche de l'utilisateur) si vous n'avez pas besoin de Prisma, Node.jssi votre route touche la base. Next.js 15 vous laisse mixer les deux route par route. Notre articleEdge Runtime + IA : latence quasi-nulle et patterns Edge Functions pour l'IA détaillent les arbitrages mesurés en production.
Accélérez avec un template prêt pour la prod
Pourquoi recâbler l'auth, Stripe et le streaming à chaque projet ? Nos templates SaaS Next.js 15 incluent Vercel AI SDK, failover multi-provider, RAG léger et billing — déployables en une commande. Voir les prix ou lire le guide de démarrage.
Explorer les templates →Conclusion : le SDK comme accélérateur, pas comme béquille
Le Vercel AI SDK ne remplace pas la compréhension des fondamentaux — prompt engineering, gestion d'erreurs, observabilité — mais il supprime la plomberie répétitive. En une journée, vous passez d'une routefetch fragile à une expérience de chat fluide, outillée et mesurable. Commencez par le streaming, ajoutez un outil métier, puis greffez un RAG léger : chaque étape apporte une valeur utilisateur immédiate sans réécrire l'existant.
L'IA dans un SaaS n'est pas une fonctionnalité — c'est une couche d'expérience. Quand elle stream instantanément, appelle les bons outils et s'appuie sur vos données, elle cesse d'être un gadget et devient un avantage concurrentiel. À vous de jouer. Nos templates vous font gagner ce temps — concentrez-vous sur votre produit, pas sur le boilerplate.