API di Runtime
createScribe e l'intera superficie del client tipizzato: lettura, helper per il routing, relazioni, sitemap, reindirizzamenti e integrazione con i framework.
View as Markdownimport { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";
const scribe = createScribe(config);
createScribe() restituisce un client tipizzato con un accessor per ogni tipo di contenuto (scribe.blog, scribe.author, ...) oltre a scribe.sitemap(). Tutte le letture sono sincrone e in memoria: il contenuto viene caricato una sola volta, memorizzato nella cache e (in fase di sviluppo) riconvalidato quando i file o l'archivio delle traduzioni cambiano.
Il client è in puro Node, senza dipendenze dai framework. Funziona allo stesso modo in Next.js, Astro, Remix, SvelteKit o in uno script di build; gli esempi di pagina qui sotto usano le convenzioni di Next.js puramente a scopo illustrativo.
I documenti presentano questa struttura, dove frontmatter è tipizzato a partire dal tuo schema Zod:
{
slug: string; // slug nella lingua di questo documento
enSlug: string; // slug genitore in inglese (== slug per i documenti EN)
locale: string;
frontmatter: { ... };
content: string; // corpo MDX grezzo
publishedAt?: string;
updatedAt?: string;
noindex: boolean;
}
Lettura
list(locale?, options?)
Tutti i documenti per una lingua (predefinita: la lingua predefinita), ordinati in base all'impostazione orderBy del tipo.
scribe.blog.list("it");
scribe.blog.list("it", { limit: 3 });
scribe.blog.list("it", { orderBy: "slug" }); // sovrascrittura per singola chiamata
Restituisce [] per una lingua senza documenti.
get(slug, locale?)
Ricerca esatta dello slug in una singola lingua. Nessun fallback e nessuna gestione dei reindirizzamenti. Restituisce il documento oppure null.
resolve(slug, locale)
La risoluzione completa del percorso di richiesta, ideale da usare all'interno delle pagine:
const r = scribe.blog.resolve(slug, locale);
// {
// document: BlogDoc | null,
// actualLocale: string, // "en" quando è intervenuto il fallback in EN
// shouldRedirectTo?: string, // target 301 (correzione dello slug per lingua errata)
// canonicalPath?: string, // percorso del documento che tiene conto della lingua
// }
Gestisce, in ordine, la corrispondenza diretta, il reindirizzamento dello slug per lingua errata, la catena di fallback delle lingue (attiva per impostazione predefinita) e infine il fallback in inglese (quando indexFallback: "en"). Quando una lingua di fallback serve la pagina, actualLocale corrisponde a quella lingua e la correzione dello slug tiene conto della catena di fallback. Le migrazioni degli slug e i documenti ritirati vengono gestiti dalle regole di _redirects.json nella mappa dei reindirizzamenti statici o del proxy, e non tramite resolve(). Un tipico gestore di pagina (nell'esempio, Next.js):
const resolved = scribe.blog.resolve(slug, locale);
if (resolved.shouldRedirectTo) permanentRedirect(resolved.shouldRedirectTo);
if (!resolved.document) notFound();
Helper per il routing
staticParams(options?)
Ogni coppia { locale, slug } da pre-renderizzare. Inseriscila nell'hook dei percorsi statici del tuo framework (ad esempio generateStaticParams per Next.js, getStaticPaths per Astro, entries per SvelteKit):
export function generateStaticParams() {
return scribe.blog.staticParams();
}
// Limita le lingue (es. una rotta per immagini OG che supporta solo alfabeti latini):
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });
Per una lingua senza traduzione, lo slug pre-renderizzato proviene dalla prima lingua della catena di fallback delle lingue che ne possiede uno (e successivamente dallo slug in inglese), in modo che gli URL pre-renderizzati corrispondano esattamente a ciò che resolve() andrà a servire.
alternates(doc)
La mappa hreflang per un documento. Mappa ogni lingua con il suo percorso relativo, per tutte le lingue che presentano una traduzione (la lingua predefinita è sempre inclusa):
scribe.blog.alternates(doc);
// { en: "/blog/hello-world", it: "/it/blog/ciao-mondo" }
Passalo ai tuoi tag hreflang, ad esempio a metadata.alternates.languages di Next.js, oppure ad elementi link renderizzati manualmente.
translation(doc, targetLocale)
Lo stesso documento in un'altra lingua, oppure null se non è tradotto. Di solito si usa fare un fallback sul documento che si ha già a disposizione:
const itDoc = scribe.blog.translation(doc, "it") ?? doc;
url(slug, locale)
Costruisce un percorso a partire dal template path del tipo:
scribe.blog.url("hello-world", "it"); // "/it/blog/hello-world"
staticParams, alternates e url generano un errore per i tipi di solo riferimento (quelli senza parametro path).
Relazioni
related(doc, field, locale?)
Dereferenzia un campo del frontmatter field.relation() nel documento o nei documenti del tipo di destinazione. Il tipo restituito viene dedotto dallo schema:
scribe.blog.related(doc, "author"); // AuthorDoc (singolo obbligatorio)
scribe.vertical.related(doc, "blogSlug"); // BlogDoc | null (singolo opzionale)
scribe.glossary.related(doc, "terms"); // GlossaryDoc[] (multiplo)
- Esegue la dereferenziazione in
locale ?? doc.locale, facendo un fallback sul documento in inglese se la destinazione non risulta tradotta. - Una relazione obbligatoria non restituisce mai
null:scribe validateblocca la compilazione in presenza di relazioni obbligatorie in sospeso, erelated()genera un'eccezione se una di queste sfugge ai controlli. - Le relazioni opzionali restituiscono
null; le relazioni multiple ignorano silenziosamente le destinazioni mancanti. - Vengono esposti solo i campi dello schema di primo livello (le relazioni annidate vengono comunque convalidate, ma andranno dereferenziate manualmente).
Sitemap
const entries = await scribe.sitemap({
baseUrl: "https://example.com",
typeDefaults: {
blog: { priority: 0.7, changeFrequency: "monthly" },
},
});
Restituisce semplici voci JSON (url, lastModified, changeFrequency, priority, l'hreflang alternates.languages comprensivo di x-default). La struttura corrisponde a MetadataRoute.Sitemap di Next.js e si serializza direttamente in formato XML per qualsiasi altro stack tecnologico. Viene inclusa una singola voce per ogni documento in inglese su tutti i tipi instradabili; vengono ignorati i documenti noindex e gli slug di origine dei reindirizzamenti presenti in _redirects.json.
Opzioni: baseUrl (obbligatorio), contentTypes, typeDefaults, resolveUrl, resolvePathname, excludeNoindex (predefinito true), includeXDefault (predefinito true).
Introspezione
Oltre agli accessor specifici per tipo, il client espone la configurazione risolta:
scribe.config; // ScribeConfig risolto
scribe.getType("blog"); // un singolo tipo di contenuto risolto
scribe.listTypes(); // tutti i tipi di contenuto
scribe.listRoutableTypes(); // solo i tipi con un template path
Utile per strumenti generici, come l'iterazione di ogni tipo instradabile al fine di costruire menu di navigazione o feed.
Reindirizzamenti
Per le regole di reindirizzamento esportate in modo statico (_redirects.json, slug cross-locale), utilizza l'entry point per gli script di build:
// scripts/generate-redirects.ts (da eseguire prima della build dell'app)
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 }, ...]
// Corrisponde perfettamente alla configurazione dei redirect di Next.js; si può mappare in base alle necessità su regole nginx, _redirects,
// vercel.json o equivalenti del tuo framework.
Esportazioni grezze statiche
Per file MDX grezzi compatibili con i crawler e i modelli LLM e serviti come asset statici (ad esempio /blog/il-mio-post.mdx):
scribe export-static --out public
O in modo programmatico:
import { writeStaticRawExports, createProject, loadConfigSync } from "scribe-cms";
writeStaticRawExports(createProject(loadConfigSync()), { outDir: "public" });
getStaticExportRoots(project) restituisce le radici delle directory gestite da ripulire prima della scrittura (ad esempio blog/, it/blog/). I documenti elencati come origini di reindirizzamento in _redirects.json vengono ignorati quando excludeRedirected è impostato su true (predefinito). I documenti noindex sono inclusi, a meno che non sia impostato excludeNoindex. Per i vari flag dei comandi, consulta il riferimento CLI.
Scappatoia
scribe.<type>.load() restituisce l'indice grezzo Map<locale, { bySlug, byEnSlug }>. È consigliabile prediligere list, get e resolve; il metodo load() esiste unicamente per la creazione di tool specifici o per modelli di accesso inusuali.
Integrazione con i framework
Scribe è agnostico rispetto ai framework. Il runtime è sviluppato in puro Node e si integra in qualsiasi stack basato sul rendering lato server o su build statiche. Si applicano sempre tre semplici regole:
- Importa da
scribe-cms/runtimenel codice dell'applicazione (per escludere i percorsi della CLI o del traduttore dal tracciamento del bundler); usascribe-cmsnegli script di build. - Mantieni
better-sqlite3(un modulo nativo) e Scribe esterni al tuo bundler, ad esempio tramiteserverExternalPackagesdi Next.js,ssr.externaldi Vite/Astro oexternaldi esbuild. - Memorizza il client nella cache all'interno di un singleton di modulo:
// 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));
}
Subordina le build allo stato di salute dei contenuti: "build": "scribe validate && <framework build>".
Esempio con Next.js
// next.config.ts
const nextConfig = {
serverExternalPackages: ["better-sqlite3", "scribe-cms", "scribe-cms/runtime"],
// Obbligatorio su Vercel: Scribe legge content/ e .scribe/ durante il runtime.
outputFileTracingIncludes: {
"/**": ["./content/**/*", "./.scribe/**/*"],
},
// Anch'esso obbligatorio su Vercel: le letture dei file di runtime di Scribe forzano il tracer
// a inserire la cache di build webpack di Next in ogni funzione serverless,
// superando il limite di 250 MB. La cache non è mai necessaria a runtime.
outputFileTracingExcludes: {
"/**": ["./.next/cache/**"],
},
};
staticParams() si innesta in generateStaticParams(), alternates() in metadata.alternates.languages, scribe.sitemap() in app/sitemap.ts e buildAllContentRedirects() in redirects().