Scribe

Runtime API

createScribeと完全な型付けを備えたクライアント機能の全容。データの読み込み、ルーティングヘルパー、リレーション、サイトマップ、リダイレクト、各種フレームワークとの統合について解説します。

View as Markdown
import { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";

const scribe = createScribe(config);

createScribe()は、各コンテンツタイプ(scribe.blogscribe.authorなど)に対するアクセサーと、scribe.sitemap()を備えた型付きクライアントを返します。読み込みはすべて同期かつインメモリで行われます。コンテンツは一度ロードされてキャッシュされ、開発環境ではファイルや翻訳ストアが変更された際に再検証されます。

このクライアントはプレーンなNode.jsで動作し、特定のフレームワークに依存しません。Next.js、Astro、Remix、SvelteKit、またはビルドスクリプト内でも同じように動作します。以下のページ例では、あくまで説明のためにNext.jsの記法を使用しています。

ドキュメントは以下の形式となります。frontmatterにはZodスキーマから推論された型が付きます。

{
  slug: string;        // このドキュメントのロケールでのスラグ
  enSlug: string;      // 英語の親スラグ(英語ドキュメントのslugと同じ)
  locale: string;
  frontmatter: { ... };
  content: string;     // 生のMDX本文
  publishedAt?: string;
  updatedAt?: string;
  noindex: boolean;
}

読み込み (Reading)

list(locale?, options?)

ロケール(デフォルトはデフォルトロケール)内の全ドキュメントを、コンテンツタイプのorderBy設定に従ってソートして取得します。

scribe.blog.list("fr");
scribe.blog.list("fr", { limit: 3 });
scribe.blog.list("fr", { orderBy: "slug" });   // 呼び出しごとの上書き

ドキュメントが存在しないロケールの場合は[]を返します。

get(slug, locale?)

単一ロケール内でのスラグによる完全一致検索です。フォールバックやリダイレクト処理は行わず、ドキュメント本体またはnullを返します。

resolve(slug, locale)

リクエストパスの完全な解決を行います。ページコンポーネント内ではこちらを使用してください。

const r = scribe.blog.resolve(slug, locale);
// {
//   document: BlogDoc | null,
//   actualLocale: string,        // 英語のフォールバックが適用された場合は"en"
//   shouldRedirectTo?: string,   // 301リダイレクト先(誤ったロケールのスラグの修正)
//   canonicalPath?: string,      // ロケールを考慮したドキュメントのパス名
// }

以下の順序で処理を行います:完全一致 → 誤ったロケールのスラグによるリダイレクト → ロケールフォールバックチェーン(デフォルトで有効) → 英語へのフォールバック(indexFallback: "en"の場合)。フォールバック先のロケールでページが提供された場合、actualLocaleにはそのロケールが入り、スラグの修正もチェーンを考慮して行われます。スラグの移行や廃止されたドキュメントの処理は、resolve()ではなく、プロキシや静的リダイレクトマップの_redirects.jsonルールで行われます。一般的なページハンドラーの例(Next.jsの場合)は以下の通りです。

const resolved = scribe.blog.resolve(slug, locale);
if (resolved.shouldRedirectTo) permanentRedirect(resolved.shouldRedirectTo);
if (!resolved.document) notFound();

ルーティングヘルパー (Routing helpers)

staticParams(options?)

プリレンダリング対象となるすべての{ locale, slug }のペアを取得します。ご利用のフレームワークの静的パス生成フック(Next.jsのgenerateStaticParams、AstroのgetStaticPaths、SvelteKitのentriesなど)にそのまま渡すことができます。

export function generateStaticParams() {
  return scribe.blog.staticParams();
}

// ロケールを制限する場合(ラテン文字のみをサポートするOG画像ルートなど):
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });

翻訳がないロケールの場合、プリレンダリングされるスラグは、翻訳が存在する最初のロケールフォールバックチェーンのロケール(または英語のスラグ)から取得されます。これにより、プリレンダリングされるURLはresolve()が提供するものと一致します。

alternates(doc)

ドキュメントのhreflangマップ(ロケールと相対パスのペア)です。翻訳が存在するすべてのロケールが含まれます(デフォルトロケールは常に含まれます)。

scribe.blog.alternates(doc);
// { en: "/blog/hello-world", fr: "/fr/blog/bonjour-le-monde" }

hreflangタグ(Next.jsのmetadata.alternates.languagesや手動でレンダリングするlink要素など)に渡して使用します。

translation(doc, targetLocale)

別のロケールでの同一ドキュメントを取得します。翻訳がない場合はnullを返します。通常は、すでに取得しているドキュメントにフォールバックさせます。

const frDoc = scribe.blog.translation(doc, "fr") ?? doc;

url(slug, locale)

コンテンツタイプのpathテンプレートからパス名を構築します。

scribe.blog.url("hello-world", "fr");  // "/fr/blog/hello-world"

staticParamsalternatesurl は、参照専用のタイプ(pathを持たないタイプ)に対して呼び出すとエラーをスローします。

リレーション (Relations)

related(doc, field, locale?)

field.relation()で定義されたfrontmatterのフィールドを解決し、ターゲットとなるドキュメントを取得します。戻り値の型はスキーマから自動的に推論されます。

scribe.blog.related(doc, "author");        // AuthorDoc           (必須の単一リレーション)
scribe.vertical.related(doc, "blogSlug");  // BlogDoc | null      (任意の単一リレーション)
scribe.glossary.related(doc, "terms");     // GlossaryDoc[]       (複数のリレーション)
  • locale ?? doc.localeで解決を行い、ターゲットが翻訳されていない場合は英語のドキュメントにフォールバックします。
  • 必須のリレーションがnullを返すことはありません。scribe validateはリンク切れの必須リレーションがあるとビルドをブロックし、すり抜けた場合でもrelated()がエラーをスローします。
  • オプショナルなリレーションはnullを返します。複数のリレーションを持つ場合は、存在しないターゲットを静かに除外します。
  • 公開されるのはトップレベルのスキーマフィールドのみです(ネストされたリレーションも検証はされますが、手動で解決する必要があります)。

サイトマップ (Sitemap)

const entries = await scribe.sitemap({
  baseUrl: "https://example.com",
  typeDefaults: {
    blog: { priority: 0.7, changeFrequency: "monthly" },
  },
});

プレーンなJSONエントリ(urllastModifiedchangeFrequencypriorityx-defaultを含むhreflang alternates.languages)を返します。この形式はNext.jsのMetadataRoute.Sitemapと一致しており、他のスタックでも直接サイトマップXMLにシリアライズできます。ルーティング可能なすべてのタイプにわたって、英語のドキュメントごとに1つのエントリが生成されます。noindexが設定されたものや、_redirects.jsonのリダイレクト元スラグはスキップされます。

オプションには、baseUrl(必須)、contentTypestypeDefaultsresolveUrlresolvePathnameexcludeNoindex(デフォルト:true)、includeXDefault(デフォルト:true)があります。

イントロスペクション (Introspection)

タイプごとのアクセサーに加えて、クライアントは解決済みの設定情報も公開します。

scribe.config;              // 解決済みの ScribeConfig
scribe.getType("blog");     // 解決済みの単一コンテンツタイプ
scribe.listTypes();         // すべてのコンテンツタイプ
scribe.listRoutableTypes(); // パステンプレートを持つタイプのみ

ナビゲーションやフィードを構築するためにルーティング可能なすべてのタイプを反復処理するなど、汎用的なツール作成に便利です。

リダイレクト (Redirects)

静的にエクスポートされるリダイレクトルール(_redirects.jsonやロケール間のスラグなど)については、ビルドスクリプトのエントリを使用します。

// scripts/generate-redirects.ts (アプリのビルド前に実行)
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 }, ...]
// Next.jsのリダイレクト設定と直接一致します。必要に応じてnginxルール、_redirects、vercel.jsonなど、ご利用のフレームワークの同等機能にマッピングしてください。

静的なRawファイルのエクスポート (Static raw exports)

LLMやクローラー向けに、静的アセットとして生MDXファイルを提供する(例:/blog/my-post.mdx)場合:

scribe export-static --out public

プログラムから実行する場合:

import { writeStaticRawExports, createProject, loadConfigSync } from "scribe-cms";

writeStaticRawExports(createProject(loadConfigSync()), { outDir: "public" });

getStaticExportRoots(project)は、書き込み前にクリーニングすべき管理対象ディレクトリのルート(例:blog/fr/blog/)を返します。excludeRedirectedがtrue(デフォルト)の場合、_redirects.jsonでリダイレクト元としてリストされているドキュメントはスキップされます。excludeNoindexが設定されていない限り、noindexのドキュメントも含まれます。コマンドフラグについては、CLIリファレンスを参照してください。

エスケープハッチ (Escape hatch)

scribe.<type>.load() は生の Map<locale, { bySlug, byEnSlug }> インデックスを返します。基本的には list / get / resolve の使用を推奨します。load() はツール作成や特殊なアクセスパターンのために用意されています。

フレームワークとの統合 (Framework integration)

Scribeは特定のフレームワークに依存しません。ランタイムはプレーンなNode.jsであり、サーバーレンダリングや静的ビルドのあらゆるスタックで機能します。すべての環境で以下の3つのルールが適用されます。

  1. アプリケーションコードでは scribe-cms/runtime からインポートしてください(バンドラーのトレースからCLIや翻訳コードのパスを除外するため)。ビルドスクリプトでは scribe-cms を使用します。
  2. ネイティブモジュールである better-sqlite3 と Scribe を、バンドラーの外部依存関係(Next.jsの serverExternalPackages、Vite/Astroの ssr.external、esbuildの external など)として設定してください。
  3. クライアントはモジュールのシングルトン内にキャッシュしてください。
// 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));
}

ビルドプロセスはコンテンツの健全性チェックを通すようにしてください: `

Runtime API · Scribe