---
order: 5
section: guides
title: Runtime API
description: >-
  Метод createScribe и весь типизированный клиентский интерфейс: чтение, хелперы
  для маршрутизации, связи, sitemap, редиректы и интеграция с фреймворками.
noindex: false
canonicalPath: /ru/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):

```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 }` для пререндера. Просто передайте их в хук вашего фреймворка (например, `generateStaticParams` в Next.js, `getStaticPaths` в Astro, `entries` в SvelteKit):

```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 (например, в `metadata.alternates.languages` для Next.js или при ручном рендере элементов 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"
```

Методы `staticParams`, `alternates` и `url` выбрасывают ошибку для ссылочных типов контента без свойства `path`.

## Связи

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

Разрешает поле frontmatter `field.relation()` в документы целевого типа. Возвращаемый тип выводится из схемы:

```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`. Множественные связи просто игнорируют недостающие цели.
- Доступны только поля схемы верхнего уровня (вложенные связи валидируются, но разрешать их нужно вручную).

## Карта сайта

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

Возвращает обычные JSON-записи (`url`, `lastModified`, `changeFrequency`, `priority`, hreflang `alternates.languages` включая `x-default`). Формат полностью соответствует `MetadataRoute.Sitemap` из Next.js и напрямую сериализуется в XML для любого другого стека. Создается одна запись на каждый английский документ среди всех маршрутизируемых типов. Игнорируются документы с `noindex` и исходные slug редиректов из `_redirects.json`.

Опции включают `baseUrl` (обязательно), `contentTypes`, `typeDefaults`, `resolveUrl`, `resolvePathname`, `excludeNoindex` (по умолчанию true) и `includeXDefault` (по умолчанию true).

## Интроспекция

Помимо доступа к конкретным типам, клиент предоставляет доступ к разрешенной конфигурации:

```ts
scribe.config;              // разрешенный ScribeConfig
scribe.getType("blog");     // один разрешенный тип контента
scribe.listTypes();         // все типы контента
scribe.listRoutableTypes(); // только типы с шаблоном пути
```

Это полезно для создания общих инструментов (например, для итерации по всем маршрутизируемым типам при создании навигации или фидов).

## Редиректы

Для статически экспортируемых правил редиректа (`_redirects.json` или кросс-локальных slug) используйте скрипт на этапе сборки:

```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

Для генерации чистых файлов MDX в виде статических ассетов (например, `/blog/my-post.mdx`), которые хорошо индексируются поисковиками и LLM:

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

Или программно:

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

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

Функция `getStaticExportRoots(project)` возвращает управляемые корневые директории для очистки перед записью (например, `blog/`, `fr/blog/`). Документы, указанные как источники редиректа в `_redirects.json`, пропускаются, если `excludeRedirected` установлено в true (по умолчанию). Документы с `noindex` включаются, если не задан флаг `excludeNoindex`. Подробнее смотрите в [справочнике CLI](/docs/cli).

## Прямой доступ

Метод `scribe.<type>.load()` возвращает сырой индекс в виде `Map<locale, { bySlug, byEnSlug }>`. Рекомендуется использовать `list`, `get` или `resolve`. Метод `load()` существует исключительно для инструментов и нестандартных сценариев доступа.

## Интеграция с фреймворками

Scribe не привязан к конкретному фреймворку. Runtime написан на чистом Node и работает в любом стеке с серверным рендерингом или статической сборкой. Везде действуют три правила:

1. Импортируйте из **`scribe-cms/runtime`** в коде приложения (это исключает CLI и код переводчика из трассировки бандлера). В скриптах сборки используйте `scribe-cms`.
2. Убедитесь, что **`better-sqlite3`** (нативный модуль) и Scribe исключены из сборки бандлера (например, через `serverExternalPackages` в Next.js, `ssr.external` в Vite/Astro или `external` в esbuild).
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 в runtime заставляет трассировщик
  // тянуть кэш сборки webpack Next в каждую бессерверную функцию.
  // Это быстро превышает лимит в 250 МБ. Кэш никогда не нужен во время выполнения.
  outputFileTracingExcludes: {
    "/**": ["./.next/cache/**"],
  },
};
```

Метод `staticParams()` легко интегрируется с `generateStaticParams()`, `alternates()` — с `metadata.alternates.languages`, `scribe.sitemap()` отлично подходит для `app/sitemap.ts`, а `buildAllContentRedirects()` встраивается в `redirects()`.
