API Runtime
createScribe et l'intégralité du client typé : lecture, aides au routage, relations, sitemap, redirections et intégration avec les frameworks.
View as Markdownimport { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";
const scribe = createScribe(config);
createScribe() renvoie un client typé doté d'un accesseur pour chaque type de contenu (scribe.blog, scribe.author, …) ainsi que scribe.sitemap(). Toutes les opérations de lecture sont synchrones et se font en mémoire : le contenu est chargé une seule fois, mis en cache et (en mode développement) revalidé lorsque les fichiers ou le magasin de traductions sont modifiés.
Le client est du pur Node, sans aucune dépendance vis-à-vis d'un framework. Il fonctionne de la même manière dans Next.js, Astro, Remix, SvelteKit ou dans un script de build. Les exemples de pages ci-dessous utilisent les conventions de Next.js à titre purement illustratif.
Les documents se présentent sous cette forme (le frontmatter est typé à partir de votre schéma Zod) :
{
slug: string; // slug dans la langue de ce document
enSlug: string; // slug parent en anglais (== slug pour les docs EN)
locale: string;
frontmatter: { ... };
content: string; // contenu brut MDX
publishedAt?: string;
updatedAt?: string;
noindex: boolean;
}
Lecture
list(locale?, options?)
Tous les documents d'une langue (par défaut : la langue principale), triés selon la propriété orderBy du type.
scribe.blog.list("fr");
scribe.blog.list("fr", { limit: 3 });
scribe.blog.list("fr", { orderBy: "slug" }); // surcharge ponctuelle
Renvoie [] pour une langue sans documents.
get(slug, locale?)
Recherche exacte du slug dans une langue. Pas de repli, pas de gestion des redirections. Renvoie le document ou null.
resolve(slug, locale)
La résolution complète du chemin de requête. C'est ce que vous devez utiliser dans vos pages :
const r = scribe.blog.resolve(slug, locale);
// {
// document: BlogDoc | null,
// actualLocale: string, // "en" lorsque le repli anglais s'est activé
// shouldRedirectTo?: string, // cible 301 (correction d'un slug dans la mauvaise langue)
// canonicalPath?: string, // chemin tenant compte de la langue pour le document
// }
Gère, dans l'ordre : correspondance directe → redirection pour cause de slug dans une mauvaise langue → chaîne de repli de langue (activé par défaut) → repli vers l'anglais (quand indexFallback: "en"). Lorsqu'une langue de repli sert la page, actualLocale correspond à cette langue et la correction du slug tient compte de la chaîne. Les migrations de slugs et les documents retirés sont gérés par les règles _redirects.json dans la carte de redirection de votre proxy ou serveur statique, et non par resolve(). Voici un exemple classique de gestionnaire de page (avec Next.js) :
const resolved = scribe.blog.resolve(slug, locale);
if (resolved.shouldRedirectTo) permanentRedirect(resolved.shouldRedirectTo);
if (!resolved.document) notFound();
Aides au routage
staticParams(options?)
Chaque paire { locale, slug } à pré-rendre. À intégrer dans le hook des chemins statiques de votre framework (generateStaticParams pour Next.js, getStaticPaths pour Astro, entries pour SvelteKit, …) :
export function generateStaticParams() {
return scribe.blog.staticParams();
}
// Restreindre les langues (par ex. pour une route d'image OG ne supportant que les alphabets latins) :
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });
Pour une langue sans traduction, le slug pré-rendu provient de la première langue de la chaîne de repli de langue qui en possède un (puis du slug anglais). Ainsi, les URL pré-rendues correspondent à ce que resolve() fournit.
alternates(doc)
La carte hreflang pour un document (langue → chemin relatif), pour chaque langue possédant une traduction (la langue par défaut est toujours incluse) :
scribe.blog.alternates(doc);
// { en: "/blog/hello-world", fr: "/fr/blog/bonjour-le-monde" }
À injecter dans vos balises hreflang, par exemple metadata.alternates.languages dans Next.js ou dans des éléments link générés manuellement.
translation(doc, targetLocale)
Le même document dans une autre langue, ou null s'il n'est pas traduit. En général, on se rabat sur le document déjà en notre possession :
const frDoc = scribe.blog.translation(doc, "fr") ?? doc;
url(slug, locale)
Construit un chemin d'accès à partir du modèle path du type :
scribe.blog.url("hello-world", "fr"); // "/fr/blog/hello-world"
Les fonctions staticParams, alternates et url déclenchent une erreur pour les types servant uniquement de référence (qui n'ont pas de path).
Relations
related(doc, field, locale?)
Déréférence un champ frontmatter field.relation() vers le(s) document(s) du type cible. Le type de retour est inféré à partir du schéma :
scribe.blog.related(doc, "author"); // AuthorDoc (unique requis)
scribe.vertical.related(doc, "blogSlug"); // BlogDoc | null (unique optionnel)
scribe.glossary.related(doc, "terms"); // GlossaryDoc[] (multiple)
- Effectue le déréférencement dans
locale ?? doc.locale, en se rabattant sur le document anglais si la cible n'est pas traduite. - Une relation requise ne renvoie jamais
null. En effet,scribe validatebloque le build en cas de relations requises non valides, etrelated()lève une exception si l'une d'elles passe à travers. - Les relations optionnelles renvoient
null; les relations multiples ignorent silencieusement les cibles manquantes. - Seuls les champs de premier niveau du schéma sont exposés (les relations imbriquées sont validées, mais vous devez les déréférencer manuellement).
Sitemap
const entries = await scribe.sitemap({
baseUrl: "https://example.com",
typeDefaults: {
blog: { priority: 0.7, changeFrequency: "monthly" },
},
});
Renvoie des entrées JSON simples (url, lastModified, changeFrequency, priority, ainsi que le hreflang alternates.languages incluant x-default). La structure correspond à MetadataRoute.Sitemap de Next.js et se sérialise directement en XML sitemap pour n'importe quelle autre stack. Une entrée par document anglais parmi tous les types routables, en ignorant les documents noindex et les slugs sources de redirection de _redirects.json.
Options : baseUrl (requis), contentTypes, typeDefaults, resolveUrl, resolvePathname, excludeNoindex (true par défaut), includeXDefault (true par défaut).
Introspection
En plus des accesseurs par type, le client expose la configuration résolue :
scribe.config; // ScribeConfig résolu
scribe.getType("blog"); // un type de contenu résolu
scribe.listTypes(); // tous les types de contenu
scribe.listRoutableTypes(); // uniquement les types avec un modèle de chemin (path)
Pratique pour les outils génériques, par exemple pour itérer sur tous les types routables afin de construire une navigation ou des flux.
Redirections
Pour les règles de redirection exportées statiquement (_redirects.json, slugs inter-langues), utilisez l'entrée du script de build :
// scripts/generate-redirects.ts (à exécuter avant le build de l'application)
import {
buildAllContentRedirects,
createProject,
createUrlBuilder,
loadConfigSync,
} from "scribe-cms";
const config = loadConfigSync();
const project = createProject(config);
const urlBuilder = createUrlBuilder(project.config);
const rules = buildAllContentRedirects(project, {
prefixedLocales: urlBuilder.prefixedLocales,
});
// [{ source, destination, permanent: true }, ...]
// Correspond directement à la config de redirection Next.js. À adapter pour des règles nginx, _redirects,
// vercel.json ou l'équivalent de votre framework selon vos besoins.
Exports bruts statiques
Pour fournir des fichiers MDX bruts optimisés pour les LLM/crawlers sous forme de ressources statiques (par exemple /blog/my-post.mdx) :
scribe export-static --out public
Ou de manière programmatique :
import { writeStaticRawExports, createProject, loadConfigSync } from "scribe-cms";
writeStaticRawExports(createProject(loadConfigSync()), { outDir: "public" });
getStaticExportRoots(project) renvoie les racines des répertoires gérés à nettoyer avant l'écriture (par ex. blog/, fr/blog/). Les documents listés comme sources de redirection dans _redirects.json sont ignorés lorsque excludeRedirected est défini sur true (par défaut). Les documents noindex sont inclus, à moins que excludeNoindex ne soit activé. Consultez la référence de la CLI pour les options de commande.
Solution de contournement (Escape hatch)
scribe.<type>.load() renvoie l'index brut Map<locale, { bySlug, byEnSlug }>. Privilégiez l'utilisation de list/get/resolve : load() existe uniquement pour l'outillage et les schémas d'accès inhabituels.
Intégration avec les frameworks
Scribe est agnostique vis-à-vis des frameworks. L'environnement d'exécution est du Node classique et fonctionne dans n'importe quelle stack rendue côté serveur ou générée statiquement. Trois règles s'appliquent partout :
- Importez depuis
scribe-cms/runtimedans le code de l'application (cela exclut les chemins de code de la CLI / du traducteur du traçage du bundler). Utilisezscribe-cmsdans les scripts de build. - Gardez
better-sqlite3(un module natif) et Scribe externes à votre bundler, par exemple viaserverExternalPackagespour Next.js,ssr.externalpour Vite/Astro, ouexternalpour esbuild. - Mettez le client en cache dans un singleton de module :
// src/lib/scribe.ts
import { createScribe } from "scribe-cms/runtime";
import type { ScribeClient } from "scribe-cms/runtime";
import config from "../../scribe.config";
let cached: ScribeClient<typeof config> | null = null;
export function getScribe() {
return (cached ??= createScribe(config));
}
Conditionnez vos builds à l'intégrité de votre contenu : "build": "scribe validate && <build du framework>".
Exemple avec Next.js
// next.config.ts
const nextConfig = {
serverExternalPackages: ["better-sqlite3", "scribe-cms", "scribe-cms/runtime"],
// Requis sur Vercel : Scribe lit content/ et .scribe/ à l'exécution.
outputFileTracingIncludes: {
"/**": ["./content/**/*", "./.scribe/**/*"],
},
// Également requis sur Vercel : les lectures de fichiers à l'exécution de Scribe
// forcent le traceur à injecter le cache de build webpack de Next dans chaque
// fonction serverless, dépassant ainsi la limite des 250 Mo. Ce cache n'est jamais nécessaire à l'exécution.
outputFileTracingExcludes: {
"/**": ["./.next/cache/**"],
},
};
staticParams() s'intègre dans generateStaticParams(), alternates() dans metadata.alternates.languages, scribe.sitemap() dans app/sitemap.ts, et buildAllContentRedirects() dans redirects().