# Fumadocs (Framework Mode, TanStack Start): 本機 Markdown（Sätteri）
URL: /zh-TW/docs/tanstack-start/integrations/content/satteri-local-md
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/integrations/content/satteri-local-md.zh-TW.mdx

適用於本機 Markdown 內容的內容來源，以 Sätteri 編譯。

## 簡介 [#introduction]

`@fumadocs/satteri/local-md` 是適用於本機 Markdown/MDX 檔案的內容來源，改用 [Sätteri](https://github.com/bruits/satteri) 編譯，而非 unified/remark 管線。

它與 [`@fumadocs/local-md`](/docs/tanstack-start/integrations/content/local-md) 的 API 相同，同樣無需打包，因此可直接在執行階段運作，不需要型別產生或打包步驟。

若您想使用 Sätteri 編譯器、它的外掛模型，以及 Fumadocs Sätteri 預設組合，請選用此內容來源。

### 差異 [#differences]

* 編譯結果一律為 JavaScript，因此不提供虛擬 JavaScript 引擎選項，算繪時會使用原生引擎。
* 外掛為 Sätteri 外掛，需透過 `satteriOptions` 設定，而非 remark/rehype 外掛。

<Callout type="warn">
請勿將不受信任的 Markdown/MDX 檔案放在 `dir` 中，因為這些檔案可能會以 JavaScript 形式執行，或參照伺服器端的檔案。
</Callout>

## 安裝設定 [#setup]

```package-install
@fumadocs/satteri shiki
```

> 之所以需要安裝 `shiki`，是因為打包工具必須將它外部化 (externalize) 。

建立 `localMd` 實例，並將它連接到 Fumadocs：

```ts title="lib/source.ts"
import { dynamicLoader } from 'fumadocs-core/source';
import { localMd } from '@fumadocs/satteri/local-md';

const docs = localMd({
  dir: 'content/docs',
});

const docsLoader = dynamicLoader(docs.dynamicSource(), {
  baseUrl: '/docs',
});

export async function getSource() {
  return docsLoader.get();
}
```

### Schema [#schema]

傳入 `frontmatterSchema` 與 `metaSchema`，即可自訂驗證用的 schema：

```ts title="lib/source.ts"
import { localMd } from '@fumadocs/satteri/local-md';
import { pageSchema, metaSchema } from 'fumadocs-core/source/schema';

const docs = localMd({
  dir: 'content/docs',
  frontmatterSchema: pageSchema.extend({
    // ...
  }),
  metaSchema: metaSchema.extend({
    // ...
  }),
});
```

### 編譯器選項 [#compiler-options]

`satteriOptions` 會經由 Fumadocs Sätteri 預設集傳入，因此外掛及其選項的設定方式與 Fumadocs MDX 相同：

```ts title="lib/source.ts"
const docs = localMd({
  dir: 'content/docs',
  satteriOptions: {
    rehypeCodeOptions: {
      themes: { light: 'github-light', dark: 'github-dark' },
    },
    mdastPlugins: [myPlugin()],
  },
});
```

傳入函式即可延遲建立選項。

## 使用方式 [#usage]

`getSource()` 會回傳一個[內容載入器](/docs/headless/source-api)。算繪頁面的方式與 [`@fumadocs/local-md`](/docs/tanstack-start/integrations/content/local-md#usage) 相同，只是改從 `@fumadocs/satteri/local-md` 匯入。

每個頁面都有 `load()`，可編譯該頁面並回傳算繪器：

```tsx
const page = source.getPage(slugs);
const { body, toc } = await (await page.data.load()).render();
```

算繪器也提供 `structuredData` 與 `exports`，可用來取得文件或其外掛所匯出的值。

### 用戶端算繪 [#client-rendering]

`serialize()` 會回傳已編譯頁面的酬載，且可安全序列化為 JSON。

請從您的 loader 回傳它，並在用戶端進行算繪：

```tsx
const serialized = (await page.data.load()).serialize();
```

```tsx title="components/content.tsx"
'use client';
import {
  rendererFromSerialized,
  type MarkdownRendererSerializedOptions,
} from '@fumadocs/satteri/local-md/client';

export function Content({ serialized }: { serialized: MarkdownRendererSerializedOptions }) {
  return rendererFromSerialized(serialized).renderSync().body;
}
```

### 引入 [#include]

[`<include>`](/docs/mdx/include) 與 `::include` 指令皆已預設啟用：

```mdx title="content/docs/page.mdx"
<include>../shared/note.md</include>
```

### 熱重載 [#hot-reload]

來源會在執行階段讀取檔案，因此預設不會監看這些檔案。

在開發期間，Vite 外掛會透過 Vite 的檔案監看器讓來源失效。請將外掛加入設定，並連接來源：

```ts tab="vite.config.ts"
import { defineConfig } from 'vite';
import { localContentPlugin } from '@fumadocs/satteri/local-md/dev/vite';

export default defineConfig({
  plugins: [localContentPlugin()],
});
```

```ts tab="lib/source.ts"
import { watchWithVite } from '@fumadocs/satteri/local-md/dev/vite';

if (import.meta.env.DEV) {
  watchWithVite(docs);
}
```

引入的檔案也會受到監看。若要自行串接監看器，請監看 `docs.dir` 以及傳入 `docs.onDependency()` 的檔案，並在檔案變更時呼叫 `docs.invalidateFile(path)`。

### 停用重新驗證 [#disable-revalidation]

若您只需要一次性的快照，請使用 `staticSource()`，如此一來，一般的 `loader()` 即可正常運作：

```ts title="lib/source.ts"
import { loader } from 'fumadocs-core/source';

export const source = loader(await docs.staticSource(), {
  baseUrl: '/docs',
});
```