Un assistant de contenu qui se place à côté de votre CMS et renvoie des champs que vous pouvez coller sans reformatage change la façon dont les éditeurs travaillent dans Webflow.
Les éditeurs de contenu qui travaillent dans le CMS Webflow perdent un temps surprenant sur les mêmes points de friction. Ils fixent un champ vide, reformatent un brouillon pour l'adapter aux champs attendus par leur collection, ou basculent entre une fenêtre de chat IA et le tableau de bord Webflow pour copier-coller du contenu.
Un assistant de contenu propulsé par Gemini, construit sur Webflow Cloud, élimine ces trois points à la fois. Il vit dans le même environnement que votre site, comprend la structure de votre collection CMS, et peut renvoyer soit un brouillon diffusé en streaming pour une écriture libre, soit un objet JSON structuré qui correspond directement aux champs de votre CMS Webflow.
Lorsque Gemini produit un objet JSON déjà formaté pour le CMS Webflow, la boucle copier-coller-reformater disparait entièrement.
Dans ce guide, nous allons construire cet assistant sur Webflow Cloud avec Next.js et l'API Gemini, puis ajouter deux modes : un mode brouillon en streaming pour l'écriture libre, et un mode CMS qui renvoie des champs que vous pouvez coller directement dans votre collection.
De quoi avez-vous besoin pour créer un assistant de contenu Gemini dans Webflow Cloud ?
Vous avez besoin d'un projet Next.js sur Webflow Cloud et d'une clé API Gemini provenant de Google AI Studio. Le SDK @google/genai communique avec Gemini via fetch, il fonctionne donc sur le runtime Workers de Webflow Cloud sans dépendances spécifiques à Node.js. N'ajoutez pas export const runtime = 'edge' à vos routes : l'adaptateur OpenNext Cloudflare utilisé par Webflow Cloud ne le prend pas en charge.
Voici la liste des prérequis :
- Un projet Webflow Cloud exécutant une application Next.js, déployée ou en développement local
- Une clé API Gemini (niveau gratuit disponible sur aistudio.google.com/apikey)
- Node.js 22.13.0 ou une version supérieure en local, le minimum requis par Webflow CLI 2.x ; le SDK
@google/genailui-même ne nécessite que Node 20 - Une connaissance de base des noms de champs de votre collection CMS Webflow (vous les référencerez dans le prompt système)
Aucun proxy IA tiers, aucune plateforme de fonctions serverless, aucun hébergement séparé. Tout s'exécute dans une seule application Webflow Cloud.
5 étapes pour créer un assistant de contenu Gemini pour Webflow
L'architecture se divise en deux parties :
- Un Route Handler en streaming qui appelle Gemini et renvoie la sortie sous forme de flux de texte
- Un composant client qui envoie le prompt de l'éditeur et affiche la réponse mot par mot
Un second endpoint, sans streaming, gère le « mode CMS » : il produit un objet JSON qui correspond directement aux champs du CMS Webflow.
J'utilise deux modes sur chaque projet. Le mode brouillon en streaming pour l'exploration libre, le mode JSON pour une sortie structurée prête pour le CMS. Les rédacteurs adorent le streaming parce que ça donne une impression de direct. J'adore le mode JSON parce qu'il supprime l'étape de reformatage du workflow de publication.
1. Obtenez une clé API Gemini et installez le SDK
Connectez-vous ou inscrivez-vous sur Google AI Studio. Cliquez sur Get API key > Create API key. Copiez la clé et conservez-la en lieu sûr.
Installez le SDK Google Gen AI dans votre projet Webflow Cloud :
npm install @google/genai
Ajoutez votre clé API à .env.local ainsi qu'au tableau de bord des variables d'environnement de Webflow Cloud, sous Settings > Environment Variables :
GEMINI_API_KEY=your_gemini_api_key_here
Ne préfixez pas cette variable avec NEXT_PUBLIC_. Toute variable préfixée par NEXT_PUBLIC_ est incluse dans le bundle JavaScript envoyé au navigateur. Une clé API Gemini publique permettrait à n'importe qui de lancer des requêtes de génération facturées sur votre compte.
2. Créez le Route Handler de streaming Gemini dans Webflow Cloud
L'endpoint de streaming reçoit un prompt de l'éditeur, appelle Gemini avec un prompt système qui définit le ton et le format de sortie, puis renvoie la réponse en streaming sous forme de texte brut. La boucle for await sur le flux Gemini alimente des morceaux dans un ReadableStream que Next.js envoie directement au navigateur.
Voici l'extrait de code :
// app/api/assist/stream/route.ts
import { GoogleGenAI } from '@google/genai'
import { type NextRequest } from 'next/server'
const SYSTEM_PROMPT = `You are a content editor for a Webflow site.
Your writing is clear, specific, and aimed at practitioners.
Avoid filler phrases, excessive hedging, and generic transitions.
Match the requested tone. Output plain prose without markdown headers
unless the user explicitly asks for structured content.`
export async function POST(request: NextRequest) {
const { topic, tone } = await request.json() as {
topic: string
tone: 'professional' | 'conversational' | 'technical'
}
const prompt = `Write a first draft about: ${topic}\nTone: ${tone}`
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! })
const stream = new ReadableStream({
async start(controller) {
try {
const responseStream = await ai.models.generateContentStream({
model: 'gemini-3.5-flash',
contents: prompt,
config: {
systemInstruction: SYSTEM_PROMPT,
maxOutputTokens: 1024,
},
})
for await (const chunk of responseStream) {
if (chunk.text) {
controller.enqueue(new TextEncoder().encode(chunk.text))
}
}
} catch (err) {
controller.error(err)
return
}
controller.close()
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}
Avec ce handler en place, un POST vers /api/assist/stream renvoie la sortie de Gemini sous forme de flux de texte en direct plutôt que comme un bloc unique. Le navigateur le lit morceau par morceau, ce qui produit l'effet machine à écrire dans l'interface que vous allez construire à l'étape 4.
La gestion des erreurs est importante ici. Appeler controller.error() lorsque Gemini lève une erreur rend l'échec visible dans le navigateur, plutôt que de fermer le flux silencieusement, ce qui serait indiscernable d'une réponse vide mais réussie.
3. Créez le Route Handler du mode CMS Gemini dans Webflow Cloud
L'endpoint du mode CMS utilise la fonctionnalité de sortie structurée de Gemini (responseMimeType: 'application/json' avec un responseSchema) pour renvoyer un objet JSON qui correspond exactement aux champs de votre CMS Webflow. L'éditeur obtient un résultat qu'il peut coller directement, sans reformatage nécessaire.
Voici le Route Handler du mode CMS qui verrouille la sortie sur le schéma de votre collection :
// app/api/assist/cms/route.ts
import { GoogleGenAI } from '@google/genai'
import { NextResponse, type NextRequest } from 'next/server'
// Adjust these field names to match your Webflow CMS collection.
const CMS_SCHEMA = {
type: 'object',
properties: {
name: { type: 'string', description: 'CMS item name, max 80 characters' },
slug: { type: 'string', description: 'URL slug, lowercase, hyphenated' },
excerpt: { type: 'string', description: 'Short summary, max 160 characters' },
body: { type: 'string', description: 'Full article body in plain text' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Up to 5 relevant tags',
},
},
required: ['name', 'slug', 'excerpt', 'body'],
}
export async function POST(request: NextRequest) {
const { topic, tone } = await request.json() as {
topic: string
tone: string
}
const prompt = `Create Webflow CMS content about: ${topic}\nTone: ${tone}`
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! })
const response = await ai.models.generateContent({
model: 'gemini-3.5-flash',
contents: prompt,
config: {
systemInstruction: `You are a Webflow content editor.
Output only valid JSON matching the provided schema.
The body field should be 300-500 words of plain text.`,
responseMimeType: 'application/json',
responseSchema: CMS_SCHEMA,
maxOutputTokens: 2048,
temperature: 0.5,
},
})
const json = JSON.parse(response.text ?? '{}')
return NextResponse.json(json)
}
Une précision sur responseSchema : la référence API de Google le signale désormais comme déprécié, bien qu'il reste entièrement documenté, sans date de suppression annoncée, et qu'il doive être associé à un responseMimeType compatible. Considérez-le comme quelque chose à revoir plus tard, pas comme quelque chose à réécrire aujourd'hui.
D'après mon expérience, le prompt système contribue davantage à la qualité de la sortie que tout autre élément de ce projet. Je passe plus de temps à ajuster l'instruction système (adapter les objectifs de nombre de mots, ajouter des descriptions de champs, préciser ce que signifie « texte brut » pour une configuration CMS donnée) qu'à écrire le code de l'application.
4. Créez l'interface de l'assistant de contenu Gemini
Le composant client gère l'état du formulaire et le lecteur de flux. Lorsque l'éditeur soumet un prompt, il ouvre un ReadableStream à partir de la réponse fetch et ajoute chaque morceau décodé à une variable d'état, ce qui produit l'effet machine à écrire.
Voici le composant client qui gère l'état du formulaire et le lecteur de flux :
// app/content-assistant/ContentAssistant.tsx
'use client'
import { useState } from 'react'
type Mode = 'draft' | 'cms'
type Tone = 'professional' | 'conversational' | 'technical'
type CmsOutput = {
name: string
slug: string
excerpt: string
body: string
tags?: string[]
}
export default function ContentAssistant() {
const [topic, setTopic] = useState('')
const [tone, setTone] = useState<Tone>('professional')
const [mode, setMode] = useState<Mode>('draft')
const [output, setOutput] = useState('')
const [cmsOutput, setCmsOutput] = useState<CmsOutput | null>(null)
const [loading, setLoading] = useState(false)
async function generate() {
if (!topic.trim() || loading) return
setLoading(true)
setOutput('')
setCmsOutput(null)
try {
if (mode === 'draft') {
const res = await fetch('/api/assist/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, tone }),
})
if (!res.ok) throw new Error('Request failed')
const reader = res.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
setOutput((prev) => prev + decoder.decode(value, { stream: true }))
}
} else {
const res = await fetch('/api/assist/cms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, tone }),
})
const data = await res.json() as CmsOutput
setCmsOutput(data)
}
} finally {
setLoading(false)
}
}
return (
<div className="content-assistant">
<h1>Gemini Content Assistant</h1>
<div className="assistant-controls">
<div className="mode-toggle">
<button
className={mode === 'draft' ? 'active' : ''}
onClick={() => setMode('draft')}
>
Draft mode
</button>
<button
className={mode === 'cms' ? 'active' : ''}
onClick={() => setMode('cms')}
>
CMS mode
</button>
</div>
<textarea
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="Describe what you want to write about..."
rows={3}
className="assistant-input"
/>
<select
value={tone}
onChange={(e) => setTone(e.target.value as Tone)}
className="assistant-tone"
>
<option value="professional">Professional</option>
<option value="conversational">Conversational</option>
<option value="technical">Technical</option>
</select>
<button
onClick={generate}
disabled={loading || !topic.trim()}
className="assistant-submit"
>
{loading ? 'Generating...' : 'Generate'}
</button>
</div>
{mode === 'draft' && output && (
<div className="assistant-output">
<pre>{output}</pre>
<button onClick={() => navigator.clipboard.writeText(output)}>
Copy to clipboard
</button>
</div>
)}
{mode === 'cms' && cmsOutput && (
<div className="assistant-cms-output">
<div className="cms-field">
<label>Name</label>
<p>{cmsOutput.name}</p>
</div>
<div className="cms-field">
<label>Slug</label>
<code>{cmsOutput.slug}</code>
</div>
<div className="cms-field">
<label>Excerpt</label>
<p>{cmsOutput.excerpt}</p>
</div>
{cmsOutput.tags && (
<div className="cms-field">
<label>Tags</label>
<p>{cmsOutput.tags.join(', ')}</p>
</div>
)}
<div className="cms-field">
<label>Body</label>
<pre>{cmsOutput.body}</pre>
</div>
<button
onClick={() => navigator.clipboard.writeText(JSON.stringify(cmsOutput, null, 2))}
>
Copy as JSON
</button>
</div>
)}
</div>
)
}
Ce composant pilote les deux modes depuis un seul formulaire. En mode brouillon, il lit le flux de réponse et ajoute chaque morceau à l'état, si bien que le texte apparaît au fur et à mesure de sa génération. En mode CMS, il attend l'objet JSON complet, puis affiche chaque champ avec son propre contrôle de copie. Les deux chemins partagent les mêmes champs de saisie pour le sujet et le ton, si bien que changer de mode n'oblige jamais l'éditeur à ressaisir quoi que ce soit.
5. Ajoutez l'assistant de contenu Gemini à une page Webflow Cloud
Créez la page qui héberge l'assistant. Comme ContentAssistant est un composant client, la page elle-même est un wrapper de composant serveur :
// app/content-assistant/page.tsx
import ContentAssistant from './ContentAssistant'
export default function ContentAssistantPage() {
return (
<main className="assistant-page">
<ContentAssistant />
</main>
)
}
L'assistant est maintenant en ligne à l'adresse /content-assistant dans votre application Webflow Cloud. Si l'application est déployée dans le cadre d'un site Webflow, montez-la sur le chemin que vous configurez dans Site Settings > Webflow Cloud. L'accès peut être restreint avec n'importe quel middleware d'authentification ; consultez le guide Supabase Auth pour limiter l'assistant aux membres d'équipe connectés.
Qu'est-ce qui casse un assistant de contenu Gemini dans Webflow ?
La plupart des échecs d'un assistant de contenu Gemini se ramènent à trois causes : une limite de tokens de sortie fixée trop bas, du JSON mal formé en mode CMS, et une clé API qui fonctionne en local mais pas en production. Chacune se manifeste à un point prévisible, et chacune a une solution rapide une fois la cause identifiée.
Voici chaque schéma d'échec.
La réponse en streaming est vide ou s'arrête au milieu d'une phrase
La cause la plus fréquente est que la limite maxOutputTokens est trop basse pour le contenu demandé. L'exemple ci-dessus la fixe à 1024 tokens, ce qui convient à un brouillon de 300 à 400 mots.
Pour du contenu plus long, augmentez maxOutputTokens (Gemini 3.5 Flash prend en charge jusqu'à 65 536 tokens de sortie). Vérifiez aussi que le bloc finally { controller.close() } est présent ; sans lui, une erreur de l'API Gemini laisse le flux ouvert, et l'interface se bloque.
La sortie JSON du mode CMS contient du texte supplémentaire ou du markdown autour du JSON
Cela se produit lorsque responseMimeType est défini, mais que le modèle ajoute une explication. Passez toujours responseMimeType: 'application/json' avec responseSchema dans la config ; sans le schéma, le modèle peut envelopper le JSON dans des blocs de code markdown.
Si du texte supplémentaire apparaît encore, ajoutez « Output only raw JSON. No explanation, no code fences. » à l'instruction système.
La clé API Gemini est rejetée en production
Vérifiez que GEMINI_API_KEY est défini dans les variables d'environnement de Webflow Cloud, sous Settings > Environment Variables, et pas uniquement dans .env.local. Les variables d'environnement définies en local ne sont pas reportées en production.
Vérifiez aussi que la clé n'a pas atteint la limite de débit du niveau gratuit ; le niveau gratuit de Gemini impose des limites de génération par minute qui peuvent être dépassées rapidement pendant les tests.
Étendez votre assistant de contenu Gemini pour les workflows CMS Webflow
L'assistant présenté dans ce guide couvre les deux schémas que j'utilise sur chaque projet : brouillons en streaming et sortie CMS structurée. Les deux s'étendent facilement à partir de là.
Pour une révision multi-tours, remplacez l'appel generateContent par ai.chats.create() et conservez l'objet de chat dans une ref React à travers les rendus. L'éditeur peut alors demander à Gemini de réécrire un paragraphe spécifique, de changer le ton ou de raccourcir l'extrait sans perdre le contexte d'origine.
Découvrez Webflow + Gemini pour les options de connexion, les capacités prises en charge et les notes de configuration qui sous-tendent ce projet.
Pour le déploiement sur Webflow Cloud et la configuration de l'environnement, consultez la documentation Webflow Cloud.
Questions fréquentes
Quel modèle Gemini devrais-je utiliser ?
gemini-3.5-flash est le bon choix par défaut pour la plupart des cas d'usage d'assistant de contenu. Il est assez rapide pour le streaming en temps réel, gère bien les longues sorties, et prend en charge la sortie JSON structurée via le paramètre responseMimeType. Pour du contenu qui nécessite un raisonnement plus poussé ou une fenêtre de contexte plus large, passez au niveau Pro de Google. Gemini 3.1 Pro (gemini-3.1-pro-preview) est aujourd'hui le modèle de classe Pro accessible via l'API, et Gemini 3.5 Pro a été annoncé lors de Google I/O 2026 mais n'est pas encore accessible via l'API. Pro échange de la vitesse contre plus de profondeur de raisonnement, ne l'utilisez donc que lorsque Flash ne suffit pas.
Le SDK @google/genai fonctionne-t-il sur Webflow Cloud ?
Oui. Le SDK @google/genai utilise l'API Fetch pour les requêtes HTTP, disponible dans le runtime Cloudflare Workers sur lequel fonctionne Webflow Cloud. N'ajoutez pas export const runtime = 'edge' à la route : le runtime edge de Next.js n'est pas pris en charge par l'adaptateur OpenNext Cloudflare, et laisser cette directive en place fait échouer le build.
Puis-je connecter l'assistant directement au CMS Webflow pour publier du contenu ?
Pas directement depuis le Route Handler, mais le chemin est simple. Une fois que l'endpoint du mode CMS renvoie un objet JSON, envoyez une requête POST à l'API Data de Webflow à l'adresse https://api.webflow.com/v2/collections/{collection_id}/items, en utilisant un token de site avec le scope CMS:write. Le JSON renvoyé par Gemini correspond à l'objet fieldData dans le corps de la requête de l'API Webflow.
Que couvre le niveau gratuit de Gemini ?
Le niveau gratuit de l'API Gemini inclut un accès à débit limité à gemini-3.5-flash, sans facturation requise. Pour un assistant de contenu utilisé par une petite équipe, le niveau gratuit suffit généralement pour les tests et un usage de production léger. Les cas d'usage à fort volume nécessitent un niveau payant. Les limites de débit varient selon le niveau d'utilisation et sont affichées par projet dans AI Studio ; consultez donc la façon dont les limites sont attribuées et la tarification par token pour les chiffres actuels.
Comment empêcher les éditeurs d'envoyer des prompts arbitraires à Gemini ?
Deux approches fonctionnent bien ensemble : valider et nettoyer le champ topic dans le Route Handler avant qu'il n'atteigne Gemini (imposer une limite de caractères, supprimer les balises HTML), et cadrer l'instruction système pour définir ce que le modèle doit faire et ne doit pas faire. Pour les équipes où le périmètre du contenu est strictement contrôlé, ajouter une liste déroulante de types de contenu prédéfinis (article de blog, description produit, annonce) remplace le champ de sujet en texte libre et maintient la génération dans les limites attendues.


