Scribe

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 Markdown
import { 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 validate blocca la compilazione in presenza di relazioni obbligatorie in sospeso, e related() 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:

  1. Importa da scribe-cms/runtime nel codice dell'applicazione (per escludere i percorsi della CLI o del traduttore dal tracciamento del bundler); usa scribe-cms negli script di build.
  2. Mantieni better-sqlite3 (un modulo nativo) e Scribe esterni al tuo bundler, ad esempio tramite serverExternalPackages di Next.js, ssr.external di Vite/Astro o external di esbuild.
  3. 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().

API di Runtime · Scribe