Construire un site moderne avec Sanity v3 et Next.js App Router

Noqta Team
Par Noqta Team ·

Chargement du lecteur de synthèse vocale...

Sanity v3 est une plateforme de contenu composable qui vous offre un Studio entièrement personnalisable, un lac de contenu en temps réel et GROQ — un des langages de requête de contenu les plus puissants disponibles. Dans ce tutoriel, vous construirez un blog complet avec prévisualisation en direct, optimisation des images et une expérience d'édition personnalisée.

Ce que vous allez construire

Un DevJournal — une plateforme de blog pour développeurs avec :

  • Sanity Studio v3 intégré dans votre application Next.js
  • Des schémas de documents personnalisés pour les articles, auteurs et catégories
  • Des requêtes GROQ pour une récupération flexible du contenu
  • Une prévisualisation en direct montrant les changements de brouillon en temps réel
  • Sanity Image avec optimisation automatique des images responsives
  • Rendu Portable Text pour le contenu riche
  • Édition visuelle avec fonctionnalité cliquer-pour-éditer
  • Revalidation à la demande via webhooks
  • Métadonnées SEO et génération d'images Open Graph
  • Stylisation avec Tailwind CSS
  • Déploiement en production sur Vercel

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 18+ installé
  • npm ou pnpm comme gestionnaire de paquets
  • Des connaissances de base en React, TypeScript et Next.js App Router
  • Un compte Sanity.io (niveau gratuit disponible sur sanity.io)
  • Un éditeur de code (VS Code recommandé)
  • Une familiarité avec les concepts de gestion de contenu

Pourquoi Sanity v3 ?

Sanity adopte une approche fondamentalement différente de la gestion de contenu. Au lieu de stocker le contenu dans une base de données traditionnelle, il utilise un lac de contenu en temps réel — un stockage de données cloud sans schéma qui se synchronise en temps réel.

FonctionnalitéSanity v3Strapi 5Payload CMS 3Contentful
ArchitectureLac de contenu hébergéServeur auto-hébergéDans Next.jsSaaS hébergé
Langage de requêteGROQ (personnalisé)REST / GraphQLREST / GraphQLREST / GraphQL
StudioApp React personnalisablePanneau adminAdmin Next.jsDashboard cloud
Temps réelIntégréConfiguration requiseConfiguration requiseWebhooks uniquement
Pipeline imagesCDN intégré + transformationsPlugin requisAdaptateur uploadCDN intégré
TarificationNiveau gratuit généreuxGratuit (auto-hébergé)Gratuit (auto-hébergé)Niveau gratuit limité
Portable TextFormat texte riche natifBlocks / MarkdownÉditeur LexicalRich text JSON
PrévisualisationSupport de première classePlugin communautaireIntégréeLimitée

Les avantages clés de Sanity : collaboration en temps réel, un langage de requête GROQ incroyablement flexible, et un Studio 100% personnalisable car il s'agit simplement d'une application React.

Étape 1 : Créer le projet Next.js

Commencez par créer une nouvelle application Next.js :

npx create-next-app@latest devjournal --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd devjournal

Étape 2 : Installer les dépendances Sanity

Installez le client Sanity, le Studio et les paquets associés :

npm install next-sanity @sanity/image-url @sanity/vision @portabletext/react
npm install -D @sanity/types

Le paquet next-sanity est l'intégration officielle qui fournit :

  • Un client Sanity configuré pour Next.js
  • Des utilitaires de prévisualisation en direct
  • Un constructeur d'URL d'images
  • Des helpers d'intégration du Studio

Étape 3 : Créer un projet Sanity

Si vous n'avez pas encore de projet Sanity, créez-en un :

npx sanity@latest init --env

Lorsque vous y êtes invité :

  • Nom du projet : devjournal
  • Utiliser la configuration par défaut du dataset ? Oui
  • Chemin de sortie du projet : Choisissez le répertoire actuel
  • Sélectionner le modèle de projet : Projet propre sans schémas prédéfinis

Cela crée un fichier .env.local avec les identifiants de votre projet :

NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
NEXT_PUBLIC_SANITY_DATASET="production"
SANITY_API_READ_TOKEN="your-read-token"

Vous pouvez également trouver votre ID de projet dans le tableau de bord Sanity sur sanity.io/manage.

Étape 4 : Configurer le client Sanity

Créez les fichiers de configuration Sanity :

// src/sanity/config.ts
export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
export const apiVersion = '2026-04-07'

Maintenant créez le client Sanity :

// src/sanity/client.ts
import { createClient } from 'next-sanity'
import { projectId, dataset, apiVersion } from './config'
 
export const client = createClient({
  projectId,
  dataset,
  apiVersion,
  useCdn: true,
})
 
// Client de prévisualisation avec token pour le contenu brouillon
export const previewClient = createClient({
  projectId,
  dataset,
  apiVersion,
  useCdn: false,
  token: process.env.SANITY_API_READ_TOKEN,
  perspective: 'previewDrafts',
})
 
export function getClient(preview = false) {
  return preview ? previewClient : client
}

Étape 5 : Définir les schémas de contenu

Les schémas Sanity définissent la structure de votre contenu. Créez des schémas pour les articles, les auteurs et les catégories.

Schéma Auteur

// src/sanity/schemas/author.ts
import { defineField, defineType } from 'sanity'
 
export const author = defineType({
  name: 'author',
  title: 'Author',
  type: 'document',
  fields: [
    defineField({
      name: 'name',
      title: 'Name',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'name',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'image',
      title: 'Image',
      type: 'image',
      options: { hotspot: true },
    }),
    defineField({
      name: 'bio',
      title: 'Bio',
      type: 'array',
      of: [{ type: 'block' }],
    }),
  ],
  preview: {
    select: {
      title: 'name',
      media: 'image',
    },
  },
})

Schéma Catégorie

// src/sanity/schemas/category.ts
import { defineField, defineType } from 'sanity'
 
export const category = defineType({
  name: 'category',
  title: 'Category',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'description',
      title: 'Description',
      type: 'text',
    }),
  ],
})

Schéma Article

// src/sanity/schemas/post.ts
import { defineField, defineType } from 'sanity'
 
export const post = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: [{ type: 'author' }],
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'mainImage',
      title: 'Main Image',
      type: 'image',
      options: { hotspot: true },
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative Text',
        },
      ],
    }),
    defineField({
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [{ type: 'reference', to: [{ type: 'category' }] }],
    }),
    defineField({
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime',
    }),
    defineField({
      name: 'excerpt',
      title: 'Excerpt',
      type: 'text',
      rows: 3,
    }),
    defineField({
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [
        { type: 'block' },
        {
          type: 'image',
          options: { hotspot: true },
          fields: [
            { name: 'alt', type: 'string', title: 'Alternative Text' },
            { name: 'caption', type: 'string', title: 'Caption' },
          ],
        },
        {
          type: 'code',
          title: 'Code Block',
        },
      ],
    }),
    defineField({
      name: 'seo',
      title: 'SEO',
      type: 'object',
      fields: [
        { name: 'metaTitle', type: 'string', title: 'Meta Title' },
        { name: 'metaDescription', type: 'text', title: 'Meta Description', rows: 3 },
        { name: 'ogImage', type: 'image', title: 'Open Graph Image' },
      ],
    }),
  ],
  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage',
    },
    prepare(selection) {
      const { author } = selection
      return { ...selection, subtitle: author ? `par ${author}` : '' }
    },
  },
  orderings: [
    {
      title: 'Date de publication, récent',
      name: 'publishedAtDesc',
      by: [{ field: 'publishedAt', direction: 'desc' }],
    },
  ],
})

Index des schémas

// src/sanity/schemas/index.ts
import { author } from './author'
import { category } from './category'
import { post } from './post'
 
export const schemaTypes = [author, category, post]

Étape 6 : Configurer Sanity Studio

Créez le fichier de configuration du Studio :

// src/sanity/studio.ts
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { visionTool } from '@sanity/vision'
import { schemaTypes } from './schemas'
import { projectId, dataset } from './config'
 
export default defineConfig({
  name: 'devjournal-studio',
  title: 'DevJournal Studio',
  projectId,
  dataset,
  plugins: [structureTool(), visionTool()],
  schema: {
    types: schemaTypes,
  },
})

Étape 7 : Intégrer le Studio dans Next.js

Créez une route pour le Sanity Studio dans votre application Next.js :

// src/app/studio/[[...tool]]/page.tsx
'use client'
 
import { NextStudio } from 'next-sanity/studio'
import config from '@/sanity/studio'
 
export default function StudioPage() {
  return <NextStudio config={config} />
}

Ajoutez un fichier layout pour empêcher le Studio d'être affecté par la mise en page de votre application :

// src/app/studio/[[...tool]]/layout.tsx
export const metadata = {
  title: 'DevJournal Studio',
}
 
export default function StudioLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="fr">
      <body>{children}</body>
    </html>
  )
}

Visitez maintenant http://localhost:3000/studio pour accéder à votre Sanity Studio. Vous verrez l'interface d'édition complète avec vos schémas personnalisés.

Étape 8 : Écrire des requêtes GROQ

GROQ (Graph-Relational Object Queries) est le langage de requête de Sanity. Il est incroyablement puissant pour récupérer exactement les données dont vous avez besoin.

Créez un fichier de requêtes :

// src/sanity/queries.ts
import { groq } from 'next-sanity'
 
// Récupérer tous les articles publiés
export const postsQuery = groq`
  *[_type == "post" && defined(publishedAt)] | order(publishedAt desc) {
    _id,
    title,
    slug,
    excerpt,
    publishedAt,
    mainImage,
    "author": author->{name, slug, image},
    "categories": categories[]->{ title, slug }
  }
`
 
// Récupérer un article par slug
export const postBySlugQuery = groq`
  *[_type == "post" && slug.current == $slug][0] {
    _id,
    title,
    slug,
    excerpt,
    publishedAt,
    mainImage,
    body,
    "author": author->{name, slug, image, bio},
    "categories": categories[]->{ title, slug },
    seo
  }
`
 
// Récupérer tous les slugs pour la génération statique
export const postSlugsQuery = groq`
  *[_type == "post" && defined(slug.current)][].slug.current
`
 
// Récupérer les articles par catégorie
export const postsByCategoryQuery = groq`
  *[_type == "post" && $categorySlug in categories[]->slug.current] | order(publishedAt desc) {
    _id,
    title,
    slug,
    excerpt,
    publishedAt,
    mainImage,
    "author": author->{name, slug, image},
    "categories": categories[]->{ title, slug }
  }
`
 
// Articles connexes
export const relatedPostsQuery = groq`
  *[_type == "post" && _id != $currentId && count(categories[@._ref in $categoryIds]) > 0] | order(publishedAt desc) [0...3] {
    _id,
    title,
    slug,
    excerpt,
    publishedAt,
    mainImage
  }
`

Remarquez comment GROQ utilise l'opérateur -> pour déréférencer les références en ligne. C'est l'une de ses fonctionnalités les plus puissantes — vous pouvez joindre des documents liés sans écrire des requêtes complexes.

Étape 9 : Configurer la gestion des images

Sanity fournit un pipeline d'images puissant avec redimensionnement, recadrage et conversion de format automatiques. Créez un utilitaire d'images :

// src/sanity/image.ts
import createImageUrlBuilder from '@sanity/image-url'
import { projectId, dataset } from './config'
import type { Image } from 'sanity'
 
const imageBuilder = createImageUrlBuilder({ projectId, dataset })
 
export function urlForImage(source: Image | undefined) {
  if (!source) return undefined
  return imageBuilder.image(source).auto('format').fit('max')
}

Créez un composant image réutilisable :

// src/components/sanity-image.tsx
import Image from 'next/image'
import { urlForImage } from '@/sanity/image'
import type { Image as SanityImageType } from 'sanity'
 
interface SanityImageProps {
  image: SanityImageType & { alt?: string }
  width?: number
  height?: number
  className?: string
  priority?: boolean
}
 
export function SanityImage({
  image,
  width = 800,
  height = 450,
  className,
  priority = false,
}: SanityImageProps) {
  const imageUrl = urlForImage(image)?.url()
  if (!imageUrl) return null
 
  return (
    <Image
      src={imageUrl}
      alt={image.alt || ''}
      width={width}
      height={height}
      className={className}
      priority={priority}
    />
  )
}

Étape 10 : Construire les pages du blog

Page de liste du blog

// src/app/blog/page.tsx
import Link from 'next/link'
import { client } from '@/sanity/client'
import { postsQuery } from '@/sanity/queries'
import { SanityImage } from '@/components/sanity-image'
 
export const revalidate = 60
 
export default async function BlogPage() {
  const posts = await client.fetch(postsQuery)
 
  return (
    <main className="mx-auto max-w-6xl px-4 py-16">
      <h1 className="mb-12 text-4xl font-bold">Blog DevJournal</h1>
 
      <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
        {posts.map((post: any) => (
          <article
            key={post._id}
            className="group overflow-hidden rounded-xl border bg-white shadow-sm transition-shadow hover:shadow-md"
          >
            <Link href={`/blog/${post.slug.current}`}>
              {post.mainImage && (
                <SanityImage
                  image={post.mainImage}
                  width={600}
                  height={340}
                  className="aspect-video w-full object-cover transition-transform group-hover:scale-105"
                />
              )}
              <div className="p-6">
                <h2 className="mb-2 text-xl font-semibold">{post.title}</h2>
                <p className="mb-4 line-clamp-2 text-gray-600">{post.excerpt}</p>
                <div className="flex items-center gap-3 text-sm text-gray-500">
                  <span>{post.author?.name}</span>
                </div>
              </div>
            </Link>
          </article>
        ))}
      </div>
    </main>
  )
}

Page d'article individuel

// src/app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { client } from '@/sanity/client'
import { postBySlugQuery, postSlugsQuery } from '@/sanity/queries'
import { SanityImage } from '@/components/sanity-image'
import { PortableTextRenderer } from '@/components/portable-text'
import type { Metadata } from 'next'
 
interface PageProps {
  params: Promise<{ slug: string }>
}
 
export async function generateStaticParams() {
  const slugs = await client.fetch(postSlugsQuery)
  return slugs.map((slug: string) => ({ slug }))
}
 
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const { slug } = await params
  const post = await client.fetch(postBySlugQuery, { slug })
  if (!post) return { title: 'Article non trouvé' }
 
  return {
    title: post.seo?.metaTitle || post.title,
    description: post.seo?.metaDescription || post.excerpt,
  }
}
 
export default async function PostPage({ params }: PageProps) {
  const { slug } = await params
  const post = await client.fetch(postBySlugQuery, { slug })
  if (!post) notFound()
 
  return (
    <article className="mx-auto max-w-3xl px-4 py-16">
      <header className="mb-12">
        <h1 className="mb-4 text-4xl font-bold leading-tight lg:text-5xl">
          {post.title}
        </h1>
        {post.excerpt && (
          <p className="mb-6 text-xl text-gray-600">{post.excerpt}</p>
        )}
      </header>
 
      {post.mainImage && (
        <SanityImage
          image={post.mainImage}
          className="mb-12 w-full rounded-xl"
          priority
        />
      )}
 
      <div className="prose prose-lg max-w-none">
        <PortableTextRenderer value={post.body} />
      </div>
    </article>
  )
}

Étape 11 : Rendre le Portable Text

Sanity utilise Portable Text — un format de texte riche qui vous donne un contrôle total sur le rendu. Créez un moteur de rendu personnalisé :

// src/components/portable-text.tsx
import {
  PortableText,
  type PortableTextComponents,
} from '@portabletext/react'
import { SanityImage } from './sanity-image'
 
const components: PortableTextComponents = {
  types: {
    image: ({ value }) => (
      <figure className="my-8">
        <SanityImage image={value} className="w-full rounded-lg" />
        {value.caption && (
          <figcaption className="mt-2 text-center text-sm text-gray-500">
            {value.caption}
          </figcaption>
        )}
      </figure>
    ),
    code: ({ value }) => (
      <pre className="my-6 overflow-x-auto rounded-lg bg-gray-900 p-4">
        <code className={`language-${value.language || 'text'}`}>
          {value.code}
        </code>
      </pre>
    ),
  },
  marks: {
    link: ({ children, value }) => (
      <a
        href={value?.href}
        rel="noreferrer noopener"
        className="text-blue-600 underline hover:text-blue-800"
      >
        {children}
      </a>
    ),
    code: ({ children }) => (
      <code className="rounded bg-gray-100 px-1.5 py-0.5 text-sm">
        {children}
      </code>
    ),
  },
  block: {
    h2: ({ children }) => (
      <h2 className="mb-4 mt-12 text-3xl font-bold">{children}</h2>
    ),
    h3: ({ children }) => (
      <h3 className="mb-3 mt-8 text-2xl font-semibold">{children}</h3>
    ),
    blockquote: ({ children }) => (
      <blockquote className="my-6 border-l-4 border-blue-500 pl-4 italic text-gray-700">
        {children}
      </blockquote>
    ),
  },
}
 
export function PortableTextRenderer({ value }: { value: any }) {
  if (!value) return null
  return <PortableText value={value} components={components} />
}

Étape 12 : Ajouter la prévisualisation en direct

La prévisualisation en direct est l'une des fonctionnalités phares de Sanity. Elle permet aux éditeurs de contenu de voir leurs modifications en temps réel avant la publication.

Créer un fournisseur de prévisualisation

// src/components/preview-provider.tsx
'use client'
 
import { LiveQueryProvider } from 'next-sanity/preview'
import { client } from '@/sanity/client'
 
export default function PreviewProvider({
  children,
  token,
}: {
  children: React.ReactNode
  token: string
}) {
  return (
    <LiveQueryProvider client={client} token={token}>
      {children}
    </LiveQueryProvider>
  )
}

Créer une route de prévisualisation

// src/app/api/preview/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const slug = searchParams.get('slug')
  const secret = searchParams.get('secret')
 
  if (secret !== process.env.SANITY_PREVIEW_SECRET) {
    return new Response('Token invalide', { status: 401 })
  }
 
  const draft = await draftMode()
  draft.enable()
  redirect(slug ? `/blog/${slug}` : '/blog')
}

Route de sortie de prévisualisation

// src/app/api/exit-preview/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
 
export async function GET() {
  const draft = await draftMode()
  draft.disable()
  redirect('/blog')
}

Étape 13 : Revalidation à la demande avec Webhooks

Au lieu de revalider sur un timer, configurez un webhook pour que Sanity déclenche la revalidation quand le contenu change :

// src/app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'
 
export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-sanity-webhook-secret')
 
  if (secret !== process.env.SANITY_WEBHOOK_SECRET) {
    return NextResponse.json({ message: 'Non autorisé' }, { status: 401 })
  }
 
  const body = await request.json()
 
  switch (body._type) {
    case 'post':
      revalidatePath('/blog')
      if (body.slug?.current) {
        revalidatePath(`/blog/${body.slug.current}`)
      }
      break
    case 'author':
    case 'category':
      revalidatePath('/blog')
      break
    default:
      revalidatePath('/')
  }
 
  return NextResponse.json({ revalidated: true, now: Date.now() })
}

Pour configurer le webhook dans Sanity :

  1. Allez sur sanity.io/manage et sélectionnez votre projet
  2. Naviguez vers API puis Webhooks
  3. Cliquez sur Create Webhook
  4. Définissez l'URL sur https://votre-domaine.com/api/revalidate
  5. Ajoutez un en-tête personnalisé : x-sanity-webhook-secret avec votre valeur secrète
  6. Sélectionnez les types de documents à déclencher (post, author, category)
  7. Choisissez les événements Create, Update et Delete

Étape 14 : Types TypeScript

Ajoutez des types TypeScript pour une sécurité de type complète :

// src/sanity/types.ts
import type { Image, Slug, PortableTextBlock } from 'sanity'
 
export interface Author {
  _id: string
  name: string
  slug: Slug
  image?: Image
  bio?: PortableTextBlock[]
}
 
export interface Category {
  _id: string
  title: string
  slug: Slug
  description?: string
  postCount?: number
}
 
export interface Post {
  _id: string
  title: string
  slug: Slug
  author: Author
  mainImage?: Image & { alt?: string }
  categories?: Category[]
  publishedAt: string
  excerpt?: string
  body?: PortableTextBlock[]
  seo?: {
    metaTitle?: string
    metaDescription?: string
    ogImage?: Image
  }
}

Étape 15 : Déployer en production

Déployer Next.js sur Vercel

git add .
git commit -m "feat: complete devjournal with Sanity v3"
git push origin main
 
npx vercel

Configurez les variables d'environnement dans Vercel :

  • NEXT_PUBLIC_SANITY_PROJECT_ID
  • NEXT_PUBLIC_SANITY_DATASET
  • SANITY_API_READ_TOKEN
  • SANITY_PREVIEW_SECRET
  • SANITY_WEBHOOK_SECRET

Configurer CORS dans Sanity

Allez sur sanity.io/manage et ajoutez votre domaine de production aux origines CORS :

  • https://votre-domaine.com (avec identifiants autorisés)
  • http://localhost:3000 (pour le développement)

Tester votre implémentation

  1. Accès au Studio : Visitez /studio et vérifiez que vous pouvez créer des auteurs, catégories et articles
  2. Liste du blog : Visitez /blog et confirmez que les articles s'affichent avec les images et métadonnées
  3. Article individuel : Cliquez sur un article et vérifiez que le Portable Text s'affiche correctement
  4. Optimisation des images : Vérifiez que les images se chargent en WebP avec des tailles responsives
  5. Prévisualisation en direct : Activez le mode brouillon et vérifiez que les changements apparaissent en temps réel
  6. Revalidation : Publiez un changement dans le Studio et vérifiez que le site se met à jour

Dépannage

Erreurs "Projet non trouvé" ou CORS

Assurez-vous que votre ID de projet est correct et que vous avez ajouté localhost:3000 aux origines CORS dans le tableau de bord Sanity.

Les images ne se chargent pas

Vérifiez que votre dataset Sanity est configuré en public pour les assets images, ou que vous transmettez le bon token pour les datasets privés.

Le Studio affiche une page blanche

Vérifiez la console du navigateur. Les problèmes courants incluent des types de schéma manquants ou une configuration de plugins incorrecte.

La requête GROQ retourne vide

Utilisez l'outil Vision dans le Studio pour tester les requêtes. Les erreurs courantes incluent des filtres _type manquants ou une syntaxe de référence incorrecte.

Prochaines étapes

  • Contenu structuré : Ajoutez plus de types de documents comme Projets, Membres d'équipe ou FAQ
  • Édition visuelle : Intégrez l'édition visuelle de Sanity pour cliquer-pour-éditer sur le frontend
  • Internationalisation : Utilisez le plugin i18n au niveau document de Sanity pour le contenu multilingue
  • Recherche : Implémentez la recherche plein texte avec la correspondance textuelle GROQ ou l'intégration Algolia
  • Analytiques : Ajoutez des compteurs de vues en utilisant les mutations Sanity depuis le frontend

Conclusion

Vous avez construit un site web complet axé sur le contenu avec Sanity v3 et Next.js App Router. Cette combinaison vous offre un Studio CMS entièrement personnalisable, un puissant langage de requête GROQ, une prévisualisation en direct en temps réel et un pipeline d'images optimisé — le tout en maintenant une sécurité de type complète avec TypeScript.

L'architecture du lac de contenu de Sanity signifie que votre contenu est toujours disponible via API, facilitant sa réutilisation sur les sites web, applications mobiles et autres plateformes. L'approche du Studio intégré signifie que les éditeurs de contenu bénéficient d'une expérience d'édition soignée sans avoir besoin d'une application séparée.

Les points clés de ce tutoriel :

  • Les schémas Sanity sont orientés code et entièrement typés
  • Les requêtes GROQ vous donnent un contrôle précis sur la récupération des données
  • Le Portable Text fournit un contenu riche avec un rendu personnalisé
  • La prévisualisation en direct et la revalidation à la demande créent un flux de travail d'édition fluide
  • Le pipeline d'images gère l'optimisation automatiquement

Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Rationalisez votre système de design : Un guide pour utiliser shadcn Registry MCP.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes