---
order: 5
section: guides
title: 运行时 API
description: createScribe 及完整的类型化客户端接口：读取、路由辅助函数、关联关系、站点地图、重定向和框架集成。
noindex: false
canonicalPath: /zh-CN/docs/runtime-api
---
```ts
import { createScribe } from "scribe-cms/runtime";
import config from "./scribe.config";

const scribe = createScribe(config);
```

`createScribe()` 会返回一个带有类型提示的客户端，每个内容类型都有一个专门的访问器（例如 `scribe.blog`、`scribe.author` 等），外加一个 `scribe.sitemap()` 方法。所有的读取操作都是同步的并且在内存中完成：内容只加载一次，随后被缓存。在开发环境下，当文件或翻译存储发生变化时会自动重新验证。

该客户端是纯 Node 实现，不依赖任何特定框架。不论是在 Next.js、Astro、Remix、SvelteKit 还是普通的构建脚本中，它的运行表现完全一致；下文出现的页面示例单纯为了演示而使用了 Next.js 规范。

文档的数据结构如下，其中 `frontmatter` 会自动根据你的 Zod schema 推导类型：

```ts
{
  slug: string;        // 当前文档语言环境下的 slug
  enSlug: string;      // 英文父级 slug（如果是英文文档，则等同于 slug）
  locale: string;
  frontmatter: { ... };
  content: string;     // 原始 MDX 内容
  publishedAt?: string;
  updatedAt?: string;
  noindex: boolean;
}
```

## 读取数据

### `list(locale?, options?)`

获取指定语言环境（未指定则使用默认语言环境）下的所有文档，并根据该内容类型的 `orderBy` 设置进行排序。

```ts
scribe.blog.list("fr");
scribe.blog.list("fr", { limit: 3 });
scribe.blog.list("fr", { orderBy: "slug" });   // 单次调用的覆盖设置
```

如果指定语言环境下没有对应的文档，则返回 `[]`。

### `get(slug, locale?)`

在单一语言环境下进行精确的 slug 匹配查找。此方法没有回退机制，也不处理重定向。直接返回匹配的文档或 `null`。

### `resolve(slug, locale)`

处理完整的请求路径解析流程，这是在页面代码中最常使用的方法：

```ts
const r = scribe.blog.resolve(slug, locale);
// {
//   document: BlogDoc | null,
//   actualLocale: string,        // 当触发英语回退时，此处为 "en"
//   shouldRedirectTo?: string,   // 301 重定向目标（修正错误语言环境下的 slug）
//   canonicalPath?: string,      // 包含语言环境的规范化文档路径
// }
```

该方法按以下顺序进行处理：精准匹配 → 错误语言环境 slug 重定向 → [语言环境回退链](/docs/configuration#locale-fallback-chains)（默认开启） → 英语回退（前提是配置了 `indexFallback: "en"`）。如果最终由回退语言环境提供了页面，`actualLocale` 将返回该语言环境，并且 slug 修正逻辑能够感知回退链的状态。需要注意的是，Slug 的历史迁移以及下线文档是由代理或静态重定向映射表中的 `_redirects.json` 规则来处理的，而不是交由 `resolve()` 负责。一个典型的页面处理逻辑如下（以 Next.js 为例）：

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

## 路由辅助函数

### `staticParams(options?)`

返回所有需要预渲染的 `{ locale, slug }` 键值对，你可以直接将它丢给框架的静态路径生成钩子（例如 Next.js 的 `generateStaticParams`、Astro 的 `getStaticPaths` 或 SvelteKit 的 `entries` 等）：

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

// 限制生成的语言环境（例如：只支持拉丁语系的 OG 图片路由）：
scribe.blog.staticParams({ locales: ["en", "fr", "de"] });
```

如果某个语言环境缺少对应的翻译内容，预渲染使用的 slug 会取自[语言环境回退链](/docs/configuration#locale-fallback-chains)中首个拥有翻译的语言环境（随后才是英语 slug），这保证了预渲染的 URL 结构与 `resolve()` 的处理结果完全一致。

### `alternates(doc)`

生成某篇文档的 hreflang 映射关系：即 语言环境 → 相对路径。这涵盖了所有提供了翻译版本的语言环境（默认语言环境必然包含在内）：

```ts
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`。调用者通常会优雅降级，回退到已获取到的文档：

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

### `url(slug, locale)`

依据该类型的 `path` 模板构建完整的路径名：

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

对于仅用作引用的类型（未定义 `path`），调用 `staticParams`、`alternates` 以及 `url` 会抛出异常。

## 关联关系

### `related(doc, field, locale?)`

将通过 `field.relation()` 声明的 frontmatter 字段解引用为目标类型的实际文档对象。其返回类型会根据 schema 自动推断：

```ts
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`；多项关系则会静默剔除丢失的目标。
- 仅暴露顶层 schema 字段（嵌套关系依然接受验证，但需要手动处理解引用）。

## 站点地图 (Sitemap)

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

返回基础 JSON 条目（包含 `url`、`lastModified`、`changeFrequency`、`priority` 以及包含 `x-default` 的 hreflang `alternates.languages`）。其数据结构完全契合 Next.js 的 `MetadataRoute.Sitemap` 格式，其他技术栈也可直接将其序列化为站点地图 XML。涵盖了所有支持路由类型中的每一篇英文文档，同时跳过被标记为 `noindex` 的文档以及位于 `_redirects.json` 中的重定向来源 slug。

可用选项：`baseUrl`（必填）、`contentTypes`、`typeDefaults`、`resolveUrl`、`resolvePathname`、`excludeNoindex`（默认 true）及 `includeXDefault`（默认 true）。

## 数据自省

除了针对具体类型的访问器，客户端还暴露出了解析后的配置对象：

```ts
scribe.config;              // 解析后的 ScribeConfig
scribe.getType("blog");     // 单个解析后的内容类型
scribe.listTypes();         // 所有的内容类型
scribe.listRoutableTypes(); // 仅包含配置了 path 模板的类型
```

这对开发通用工具来说非常实用，例如遍历每一个支持路由的内容类型以生成导航栏或订阅源。

## 重定向

针对静态导出的重定向规则（例如 `_redirects.json` 或跨语言环境的 slugs），请使用这个用于构建脚本的入口点：

```ts
// 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 或是你选用框架对应的类似配置。
```

## 原始静态导出

如果需要将原始 MDX 文件以静态资源的形式提供（方便 LLM 获取或爬虫抓取，如 `/blog/my-post.mdx`）：

```bash
scribe export-static --out public
```

或者通过代码调用：

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

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

`getStaticExportRoots(project)` 会返回所有由工具托管的根目录，供写入前清理使用（如 `blog/`、`fr/blog/`）。当 `excludeRedirected` 设置为 true 时（默认开启），在 `_redirects.json` 中列为重定向源的文档将被跳过。除非配置了 `excludeNoindex`，否则 `noindex` 文档也会被包括在内。请参阅[命令行参考](/docs/cli)了解具体的命令参数标志。

## 逃生舱接口

`scribe.<type>.load()` 可以让你获取最底层的 `Map<locale, { bySlug, byEnSlug }>` 索引数据。在绝大多数情况下，建议优先使用 `list`、`get` 及 `resolve`；`load()` 是专为开发工具及应对不寻常的访问模式而保留的。

## 框架集成

Scribe 是框架无关的；运行时基于纯 Node 构建，适用于任何服务端渲染或者静态构建方案。不论在何处部署，始终需遵循这三条准则：

1. 在业务代码中请从 **`scribe-cms/runtime`** 引入依赖（这能阻止打包工具追踪 CLI 和翻译工具的代码路径）；在构建脚本中则引入 `scribe-cms`。
2. 将 **`better-sqlite3`**（原生模块）与 Scribe 配置为打包器的外部依赖。比如 Next.js 中的 `serverExternalPackages`、Vite/Astro 的 `ssr.external` 或是 esbuild 的 `external` 配置项。
3. 在单例模块中缓存客户端实例：

```ts
// 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));
}
```

让构建流程依赖内容状态的健康度验证：`"build": "scribe validate && <framework build>"`。

### Next.js 示例

```ts
// next.config.ts
const nextConfig = {
  serverExternalPackages: ["better-sqlite3", "scribe-cms", "scribe-cms/runtime"],
  // Vercel 部署必需：Scribe 会在运行时读取 content/ 与 .scribe/ 目录。
  outputFileTracingIncludes: {
    "/**": ["./content/**/*", "./.scribe/**/*"],
  },
  // Vercel 部署必需：Scribe 在运行时的文件读取会触发追踪器将 Next 的
  // webpack 构建缓存带入每个 Serverless 函数中，这通常会突破 250 MB 限制。
  // 运行时其实根本不需要这些缓存。
  outputFileTracingExcludes: {
    "/**": ["./.next/cache/**"],
  },
};
```

`staticParams()` 完美对应 `generateStaticParams()`，`alternates()` 对应 `metadata.alternates.languages`，`scribe.sitemap()` 对应 `app/sitemap.ts`，而 `buildAllContentRedirects()` 则是为 `redirects()` 量身定做的。
