API de Runtime
createScribe e a interface completa do client tipado: leitura, helpers de roteamento, relacionamentos, sitemap, redirecionamentos e integração com frameworks.
View as Markdownimport { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";
const scribe = createScribe(config);
O createScribe() retorna um client tipado com um accessor para cada tipo de conteúdo (scribe.blog, scribe.author, ...) além do scribe.sitemap(). Todas as leituras são síncronas e em memória. O conteúdo é carregado uma vez, guardado em cache e, durante o desenvolvimento, revalidado sempre que os arquivos ou o repositório de traduções sofrem alterações.
O client usa Node puro e não tem dependências de framework. Ele funciona da mesma forma no Next.js, Astro, Remix, SvelteKit ou em um script de build. Os exemplos de página abaixo usam as convenções do Next.js apenas para fins de ilustração.
Os documentos seguem este formato, e o frontmatter é tipado a partir do seu schema do Zod:
{
slug: string; // slug in this document's locale
enSlug: string; // English parent slug (== slug for EN docs)
locale: string;
frontmatter: { ... };
content: string; // raw MDX body
publishedAt?: string;
updatedAt?: string;
noindex: boolean;
}
Leitura
list(locale?, options?)
Todos os documentos para um idioma (o padrão é o idioma principal), ordenados pela propriedade orderBy do tipo de conteúdo.
scribe.blog.list("fr");
scribe.blog.list("fr", { limit: 3 });
scribe.blog.list("fr", { orderBy: "slug" }); // per-call override
Retorna [] se o idioma não tiver documentos.
get(slug, locale?)
Busca o slug exato em um idioma específico. Não possui fallback nem tratamento de redirecionamentos. Retorna o documento ou null.
resolve(slug, locale)
A resolução completa do caminho da requisição. Use isto nas suas páginas:
const r = scribe.blog.resolve(slug, locale);
// {
// document: BlogDoc | null,
// actualLocale: string, // "en" when EN fallback kicked in
// shouldRedirectTo?: string, // 301 target (wrong-locale slug correction)
// canonicalPath?: string, // locale-aware pathname for the document
// }
O método lida com a seguinte ordem: acerto direto, redirecionamento de slug no idioma errado, cadeia de fallback de idiomas (ativado por padrão) e fallback para o inglês (quando configurado como indexFallback: "en"). Quando a página é servida por um idioma de fallback, o actualLocale reflete esse idioma e a correção de slug respeita a cadeia configurada. Migrações de slug e documentos aposentados são gerenciados por regras no arquivo _redirects.json dentro do seu proxy ou mapa de redirecionamento estático, não pelo resolve(). Veja um exemplo de handler de página (usando Next.js):
const resolved = scribe.blog.resolve(slug, locale);
if (resolved.shouldRedirectTo) permanentRedirect(resolved.shouldRedirectTo);
if (!resolved.document) notFound();
Helpers de roteamento
staticParams(options?)
Todos os pares { locale, slug } a serem pré-renderizados. Basta usar essa função diretamente no hook de caminhos estáticos do seu framework (generateStaticParams no Next.js, getStaticPaths no Astro, entries no SvelteKit, ...):
export function generateStaticParams() {
return scribe.blog.staticParams();
}
// Restrict locales (e.g. an OG-image route that only supports Latin scripts):
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });
Para idiomas sem tradução, o slug pré-renderizado será extraído do primeiro idioma disponível na cadeia de fallback de idiomas (ou então o slug em inglês). Assim, as URLs pré-renderizadas combinam perfeitamente com o que o resolve() entrega.
alternates(doc)
O mapa de hreflang para um documento (idioma com seu respectivo caminho relativo) aplicável a todos os idiomas que possuem tradução (o idioma principal sempre é incluído):
scribe.blog.alternates(doc);
// { en: "/blog/hello-world", fr: "/fr/blog/bonjour-le-monde" }
Use essa saída para preencher suas tags hreflang, como no metadata.alternates.languages do Next.js ou na renderização manual de elementos link.
translation(doc, targetLocale)
O mesmo documento em outro idioma, ou null caso não haja tradução. Geralmente, faz-se o fallback para o documento que já se tem em mãos:
const frDoc = scribe.blog.translation(doc, "fr") ?? doc;
url(slug, locale)
Constrói um caminho de URL baseado no template path daquele tipo de conteúdo:
scribe.blog.url("hello-world", "fr"); // "/fr/blog/hello-world"
Os métodos staticParams, alternates e url lançam exceções caso sejam usados com tipos que servem apenas de referência (sem a configuração path).
Relacionamentos
related(doc, field, locale?)
Converte um campo field.relation() do frontmatter nos documentos do tipo correspondente. O tipo retornado é inferido automaticamente a partir do schema:
scribe.blog.related(doc, "author"); // AuthorDoc (required single)
scribe.vertical.related(doc, "blogSlug"); // BlogDoc | null (optional single)
scribe.glossary.related(doc, "terms"); // GlossaryDoc[] (multiple)
- Resolve as referências usando
locale ?? doc.localee usa o documento em inglês como fallback caso o alvo não tenha tradução. - Um relacionamento obrigatório nunca retorna
null. O comandoscribe validateinterrompe o build se houver referências obrigatórias vazias, e a funçãorelated()lança um erro caso alguma escape. - Relacionamentos opcionais retornam
null. Já os relacionamentos múltiplos simplesmente ignoram de forma silenciosa os alvos que estiverem faltando. - Somente os campos de primeiro nível do schema ficam expostos (relacionamentos aninhados ainda são validados, mas você precisa resolvê-los manualmente).
Sitemap
const entries = await scribe.sitemap({
baseUrl: "https://example.com",
typeDefaults: {
blog: { priority: 0.7, changeFrequency: "monthly" },
},
});
Retorna objetos JSON simples com os campos url, lastModified, changeFrequency, priority e as tags hreflang de alternates.languages (incluindo x-default). Esse formato é compatível com o MetadataRoute.Sitemap do Next.js e pode ser serializado diretamente em um XML de sitemap para qualquer outra stack. Há uma entrada para cada documento em inglês de todos os tipos de conteúdo roteáveis. O sitemap ignora documentos marcados como noindex e também slugs de origem de redirecionamento do arquivo _redirects.json.
Opções: baseUrl (obrigatório), contentTypes, typeDefaults, resolveUrl, resolvePathname, excludeNoindex (o padrão é true) e includeXDefault (o padrão é true).
Introspecção
Além dos accessors específicos por tipo, o client também expõe as configurações resolvidas:
scribe.config; // resolved ScribeConfig
scribe.getType("blog"); // one resolved content type
scribe.listTypes(); // all content types
scribe.listRoutableTypes(); // only types with a path template
Essa característica é ótima para criar ferramentas genéricas, como iterar sobre todos os tipos roteáveis para construir menus de navegação ou feeds.
Redirecionamentos
Para lidar com regras de redirecionamento exportadas de forma estática (como o arquivo _redirects.json ou slugs que variam conforme o idioma), utilize a entrada do script de build:
// scripts/generate-redirects.ts (run before the app build)
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 }, ...]
// Matches Next.js redirect config directly; map to nginx rules, _redirects,
// vercel.json, or your framework's equivalent as needed.
Exportações estáticas puras
Se você precisar fornecer arquivos MDX puros como assets estáticos para facilitar a leitura por LLMs ou crawlers (por exemplo, /blog/my-post.mdx):
scribe export-static --out public
Ou então, faça isso de forma programática:
import { writeStaticRawExports, createProject, loadConfigSync } from "scribe-cms";
writeStaticRawExports(createProject(loadConfigSync()), { outDir: "public" });
A função getStaticExportRoots(project) retorna os diretórios raiz gerenciados que devem ser limpos antes da gravação (como blog/ ou fr/blog/). Os documentos listados como origem de redirecionamentos no arquivo _redirects.json são pulados caso a opção excludeRedirected esteja como true (que é o comportamento padrão). Documentos marcados com noindex são incluídos na exportação, a menos que a opção excludeNoindex esteja ativada. Consulte a referência da CLI para verificar as flags do comando.
Válvula de escape
O método scribe.<type>.load() devolve o índice puro no formato Map<locale, { bySlug, byEnSlug }>. Dê preferência aos métodos list, get ou resolve. A função load() existe especificamente para criar integrações ou lidar com padrões de acesso pouco comuns.
Integração com frameworks
O Scribe é independente de framework. O runtime funciona com Node puro e se adapta a qualquer stack renderizada no servidor ou compilada de forma estática. Independentemente do seu ambiente, três regras se aplicam sempre:
- No código da aplicação, importe sempre de
scribe-cms/runtime(isso impede que o bundler rastreie os códigos da CLI ou do tradutor). Use apenasscribe-cmsnos scripts de build. - Mantenha o
better-sqlite3(que é um módulo nativo) e o Scribe marcados como externos no seu bundler. Isso significa usarserverExternalPackagesno Next.js,ssr.externalno Vite ou Astro, eexternalno esbuild. - Crie um cache do client através de um módulo singleton:
// 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));
}
Garanta que a integridade do conteúdo seja validada antes de seguir com o build. Utilize algo como "build": "scribe validate && <seu comando de build do framework>".
Exemplo no Next.js
// next.config.ts
const nextConfig = {
serverExternalPackages: ["better-sqlite3", "scribe-cms", "scribe-cms/runtime"],
// Required on Vercel: Scribe reads content/ and .scribe/ at runtime.
outputFileTracingIncludes: {
"/**": ["./content/**/*", "./.scribe/**/*"],
},
// Also required on Vercel: Scribe's runtime file reads make the tracer
// sweep Next's webpack build cache into every serverless function,
// blowing past the 250 MB limit. The cache is never needed at runtime.
outputFileTracingExcludes: {
"/**": ["./.next/cache/**"],
},
};
O staticParams() se encaixa de forma natural no generateStaticParams(), o alternates() no metadata.alternates.languages, o scribe.sitemap() dentro de app/sitemap.ts, e a função buildAllContentRedirects() nas configurações de redirects().