---
title: "Vite Integration Plugins"
description: "Fumadocs provides Vite-specific integration plugins to support non-Next.js environments, configuring core loading pipelines, virtual files, and module resolution for MDX and metadata documents."
last_updated: "2026-10-12T07:53:59.736226+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/vite-integration-plugins"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts)
- [packages/vite-data/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite-data/package.json)
- [packages/vite/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/server.ts)
- [packages/mdx/src/rolldown/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/rolldown/index.ts)
- [packages/story/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts)
- [packages/local-md/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/index.ts)
- [packages/openapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/server/index.tsx)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/server/index.tsx)
- [packages/mdx/src/bun/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/bun/index.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/dynamic.ts)
- [packages/local-md/src/dev/react-client.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts)
- [packages/preview/waku.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/waku.config.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts)
- [packages/sanity/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/sanity/src/index.ts)
- [packages/twoslash/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/twoslash/src/index.ts)
- [packages/cli/src/registry/plugins/preserve.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/registry/plugins/preserve.ts)
- [packages/vite/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/package.json)
- [packages/mdx/src/loaders/mdx/remark-include.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/mdx/remark-include.ts)
- [packages/mdx/src/plugins/json-schema.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/json-schema.ts)
- [packages/mdx/src/core.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/core.ts)
- [packages/mdx/src/runtime/types.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/types.ts)
- [packages/mdx/src/loaders/meta.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/loaders/meta.ts)
- [packages/content-collections/src/configuration.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/configuration.ts)
- [packages/local-md/src/dev/shared.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/shared.ts)
- [packages/content-collections/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content-collections/src/index.ts)
- [packages/preview/src/lib/source/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/index.ts)
</details>

## Overview

Fumadocs provides Vite-specific integration plugins to support non-Next.js environments, configuring core loading pipelines, virtual files, and module resolution for MDX and metadata documents.

Sources: [packages/mdx/src/vite/index.ts:1-168](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts#L1-L168)

## Vite Ecosystem Architecture

### Subsystem Composition

The `@fumadocs/vite` package acts as an internal configuration and build tooling provider for non-Next.js setups, working in conjunction with `vite-data` to orchestrate workspace dependencies. During initialization in `tsdown.config.ts`, `@fumadocs/vite` crawls the `../vite-data` workspace package to inspect workspace dependencies, generating a `src/generated.json` file that controls Vite optimization and SSR bundling parameters.

Sources: [packages/vite/tsdown.config.ts:1-22](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts#L1-L22)

The `vite-data` package declares workspace dependencies across multiple UI, content source, and processing packages, defining the scope of packages available for framework crawling and dependency optimization.

```json
"dependencies": {
  "@fumadocs/base-ui": "workspace:*",
  "@fumadocs/basehub": "workspace:*",
  "@fumadocs/local-md": "workspace:*",
  "@fumadocs/mdx-remote": "workspace:*",
  "@fumadocs/sanity": "workspace:*",
  "@fumadocs/story": "workspace:*",
  "fumadocs-core": "workspace:*",
  "fumadocs-mdx": "workspace:*",
  "fumadocs-twoslash": "workspace:*",
  "fumadocs-typescript": "workspace:*",
  "fumadocs-ui": "workspace:*"
}
```
Sources: [packages/vite-data/package.json:5-17](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite-data/package.json#L5-L17)

### Build Configuration and Package Metadata

Package distribution for `@fumadocs/vite` is managed via `tsdown` with ECMAScript module formatting targeting `es2023`. The build configuration disables dts sourcemaps, preserves extension behavior, and specifies an entry point at `src/index.ts` with automatic exports resolution.

```typescript
export default defineConfig({
  dts: {
    sourcemap: false,
  },
  fixedExtension: false,
  target: 'es2023',
  format: 'esm',
  entry: ['src/index.ts'],
  deps: {
    onlyBundle: [],
  },
  exports: true,
});
```
Sources: [packages/vite/tsdown.config.ts:26-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts#L26-L38)

The package metadata defines private status, versioning, repository pointers, and standard development scripts for building, linting, and type checking.

| Script Name | Command | Purpose |
| :--- | :--- | :--- |
| `build` | `tsdown` | Compiles the package using tsdown |
| `clean` | `rimraf dist` | Removes the `dist` output directory |
| `dev` | `tsdown --watch --clean false` | Runs tsdown in watch mode without cleaning |
| `lint` | `oxlint .` | Lints the codebase using oxlint |
| `types:check` | `tsc --noEmit` | Verifies TypeScript types without emitting output |

Sources: [packages/vite/package.json:1-29](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/package.json#L1-L29)

> [!NOTE]
> The `vite-data` package functions purely as a metadata manifest workspace package (`"private": true`) rather than a distributable runtime library, providing workspace references that `@fumadocs/vite` processes during build time to configure dependency optimization and server-side rendering externalization rules.

## Framework Package Crawling

### Crawling Configuration and Filtering

The build initialization script inside `packages/vite/tsdown.config.ts` invokes `crawlFrameworkPkgs` to scan the workspace root located at `../vite-data`. During the crawl process, package names are filtered using the `isFrameworkPkgByName` callback function, which matches packages starting with `@fumadocs/` or `fumadocs-`.

```typescript
async function init() {
  const out = await crawlFrameworkPkgs({
    root: '../vite-data',
    isFrameworkPkgByName(pkgName) {
      if (pkgName.startsWith('@fumadocs/') || pkgName.startsWith('fumadocs-')) return true;
    },
  });
```
Sources: [packages/vite/tsdown.config.ts:6-12](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts#L6-L12)

> [!NOTE]
> The framework package filter strictly identifies dependencies matching `@fumadocs/` or `fumadocs-` prefixes to determine which workspace packages require inclusion in Vite's dependency optimization and server-side rendering bundling rules.

### Generated Configuration Output

The crawling output produces an object containing dependency optimization lists and server-side rendering settings. These properties are mapped into a `Generated` type structure and written directly to `src/generated.json` via asynchronous file system operations before the `tsdown` build bundle step executes.

```typescript

  const generated: Generated = {
    optimizeDeps: out.optimizeDeps,
    ssr: {
      noExternal: out.ssr.noExternal,
    },
  };

  await writeFile('src/generated.json', JSON.stringify(generated));
}
```
Sources: [packages/vite/tsdown.config.ts:13-22](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts#L13-L22)

| Property Path | Source Object Key | Target Output File | Purpose |
| :--- | :--- | :--- | :--- |
| `optimizeDeps` | `out.optimizeDeps` | `src/generated.json` | Configures Vite dependency pre-bundling optimizations |
| `ssr.noExternal` | `out.ssr.noExternal` | `src/generated.json` | Forces specified framework packages to be bundled during SSR |

Sources: [packages/vite/tsdown.config.ts:14-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/vite/tsdown.config.ts#L14-L21)

## Story Component Vite Integration

### Story Plugin Options and Initialization

The `@fumadocs/story` package exports a default Vite plugin function that processes story component definitions. The plugin accepts optional configuration parameters conforming to the `StoryPluginOptions` interface, which controls TypeScript project resolution and file matching.

| Option Name | Type | Default Value | Purpose |
| :--- | :--- | :--- | :--- |
| `tsconfigPath` | `string` | `"tsconfig.json"` | Path to the TypeScript project configuration file |
| `filter` | `RegExp` | `/\.story\.(js|jsx|ts|tsx)$/` | Regular expression filtering story files for transformation |

Sources: [packages/story/src/vite/index.ts:7-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L7-L21)

```typescript
export default function story(pluginOptions: StoryPluginOptions = {}): Plugin {
  const { filter = /\.story\.(js|jsx|ts|tsx)$/ } = pluginOptions;
  let tsconfigPath: string;
  let projectPromise: Promise<Project> | undefined;

  return {
    name: 'fumadocs:story',
    enforce: 'pre',
    configResolved(resolved) {
      tsconfigPath = pluginOptions.tsconfigPath ?? path.join(resolved.root, 'tsconfig.json');
    },
```
Sources: [packages/story/src/vite/index.ts:23-33](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L23-L33)

> [!NOTE]
> The `configResolved` hook resolves the absolute path to `tsconfig.json` relative to the Vite root directory if an explicit `tsconfigPath` option is omitted by the consumer.

### Transformation Pipeline and AST Project Creation

The plugin registers with `enforce: 'pre'` to intercept story files before standard build transformations occur. When Vite encounters a module matching the filter pattern during compilation, the plugin invokes the transformation handler.

```typescript
  transform: {
    filter: { id: filter },
    async handler(code, id) {
      projectPromise ??= createControlsProject(tsconfigPath);

      return transformStoryFile('@fumadocs/story/vite/client', code, id, await projectPromise);
    },
  },
};
```
Sources: [packages/story/src/vite/index.ts:34-42](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L34-L42)

> [!WARNING]
> The `ts-morph` project instance (`projectPromise`) is lazily initialized on the first transform request using `createControlsProject(tsconfigPath)`, preventing unnecessary disk I/O and AST parsing overhead during cold startup for builds that contain no story files.

The transformation pipeline passes the virtual client import specifier `'@fumadocs/story/vite/client'`, the raw source code string, the module identifier `id`, and the resolved `Project` instance to `transformStoryFile` for AST rewriting and controls generation.

```typescript
projectPromise ??= createControlsProject(tsconfigPath);

return transformStoryFile('@fumadocs/story/vite/client', code, id, await projectPromise);
```
Sources: [packages/story/src/vite/index.ts:37-39](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/story/src/vite/index.ts#L37-L39)

## Local Markdown Dev Server

### WebSocket Transport and Client Event Protocol

The local markdown system provides live hot reloading through WebSocket communication between the Node.js development server and browser clients. The shared utilities define standard socket event types for directory monitoring and server notifications. Client events transmit watch configurations containing target directories and file inclusion globs, while server events report file alterations or error conditions.

| Event Type | Structure / Fields | Purpose |
| :--- | :--- | :--- |
| `'watch-dir'` | `{ type: 'watch-dir', dir: string, includes: string[] }` | Registers a root directory and glob patterns for file system tracking |
| `'change'` | `{ type: 'change', event: DevWatchEvent, absolutePath: string, timestamp: number }` | Notifies clients when a content file is added, modified, or unlinked |
| `'error'` | `{ type: 'error', message: string, timestamp: number }` | Transmits diagnostic error messages from the development server |

Sources: [packages/local-md/src/dev/shared.ts:3-25](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/shared.ts#L3-L25)

JSON encoding and decoding helpers marshal these event objects for transmission across the WebSocket stream, safely catching parsing exceptions during message handling.

```typescript
export function encodeDevClientEvent(event: DevClientEvent): string {
  return JSON.stringify(event);
}

export function decodeDevClientEvent(value: string): DevClientEvent | undefined {
  try {
    return JSON.parse(value) as DevClientEvent;
  } catch {
    return undefined;
  }
}

export function encodeDevEvent(event: DevServerEvent): string {
  return JSON.stringify(event);
}

export function decodeDevEvent(value: string): DevServerEvent | undefined {
  try {
    return JSON.parse(value) as DevServerEvent;
  } catch {
    return undefined;
  }
}
```
Sources: [packages/local-md/src/dev/shared.ts:27-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/shared.ts#L27-L49)

> [!NOTE]
> Socket server URLs are resolved by inspecting environment variables in a specific precedence order across different bundler runtimes and frameworks.

### Environment Variable Resolution and Fallbacks

The dev server URL lookup checks three distinct environment variable names to support Next.js, Vite, and standalone Node.js environments.

| Environment Variable Name | Target Runtime / Context |
| :--- | :--- |
| `FD_LOCAL_MD_DEV_SERVER_URL` | Standard Node.js environment |
| `NEXT_PUBLIC_FD_LOCAL_MD_DEV_SERVER_URL` | Next.js client/server bundles |
| `VITE_FD_LOCAL_MD_DEV_SERVER_URL` | Vite build configurations |

Sources: [packages/local-md/src/dev/shared.ts:51-55](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/shared.ts#L51-L55)

The `getDevServerUrlFromEnv` function checks `process.env` first, falling back to `import.meta.env` when executed in ESM-native bundlers like Vite.

```typescript
export function getDevServerUrlFromEnv(): string | undefined {
  // must hardcode to allow bundlers to inline env variables
  if (typeof process === 'object' && 'env' in process) {
    return (
      process.env.FD_LOCAL_MD_DEV_SERVER_URL ??
      process.env.NEXT_PUBLIC_FD_LOCAL_MD_DEV_SERVER_URL ??
      process.env.VITE_FD_LOCAL_MD_DEV_SERVER_URL
    );
  }

  if (import.meta.env) {
    return (
      import.meta.env.FD_LOCAL_MD_DEV_SERVER_URL ??
      import.meta.env.NEXT_PUBLIC_FD_LOCAL_MD_DEV_SERVER_URL ??
      import.meta.env.VITE_FD_LOCAL_MD_DEV_SERVER_URL
    );
  }
}
```
Sources: [packages/local-md/src/dev/shared.ts:57-74](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/shared.ts#L57-L74)

> [!WARNING]
> Environment variable names must be hardcoded as literal strings in source code to allow bundlers such as Vite and Next.js to statically inline their values during compilation.

### React Client Integration and HMR Refresh

The browser-side runtime is encapsulated in the `DevClient` React component, decorated with the `'use client'` directive. It establishes a WebSocket connection to the dev server URL upon mounting and cleans up the socket connection on unmount.

```typescript
useEffect(() => {
  const url = getDevServerUrlFromEnv();
  if (!url) return;

  const ws = new WebSocket(url);
  ws.onopen = () => {
    console.log(`[@fumadocs/local-md] connected to dev server at ${url}`);
  };

  ws.onmessage = (event) => {
    const decoded = decodeDevEvent(String(event.data));
    if (decoded) onUpdate(decoded);
  };

  ws.onclose = () => {
    console.log(`[@fumadocs/local-md] disconnected from dev server at ${url}`);
  };

  return () => {
    ws.close();
  };
}, []);

```
Sources: [packages/local-md/src/dev/react-client.ts:16-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts#L16-L38)

When an incoming message arrives over the WebSocket, `decodeDevEvent` parses the payload. If the decoded event type matches `'change'`, the client logs the update and invokes `router.refresh()` from `fumadocs-core/framework` to trigger framework-level hot module replacement and re-fetch updated markdown content.

```typescript
export function DevClient(): ReactNode {
  const router = useRouter();

  const onUpdate = useEffectEvent((event: DevServerEvent) => {
    if (event.type === 'change') {
      console.log(`[@fumadocs/local-md] "${event.absolutePath}" updated`);
      router.refresh();
    }
  });
```
Sources: [packages/local-md/src/dev/react-client.ts:6-14](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/local-md/src/dev/react-client.ts#L6-L14)

## Preview Server Bundler Wiring

### Waku Configuration and Vite Plugin Integration

The built-in [Preview Server](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/preview-server) relies on a Waku configuration file that sets up distribution directories and injects Tailwind CSS along with Fumadocs Vite plugins[Preview Server](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/preview-server). The `defineConfig` utility from Waku specifies `dist/waku` as the output distribution directory and configures the underlying Vite bundler with `tsconfigPaths` resolution enabled.

```typescript
import { defineConfig } from 'waku/config';
import tailwindcss from '@tailwindcss/vite';
import { getConfig } from '@fumadocs/vite';

export default defineConfig({
  distDir: 'dist/waku',
  vite: {
    resolve: {
      tsconfigPaths: true,
    },

    plugins: [
      tailwindcss(),
      {
        name: 'internal',
        config() {
          return getConfig({ root: process.cwd() });
        },
      },
    ],
  },
```
Sources: [packages/preview/waku.config.ts:1-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/waku.config.ts#L1-L21)

> [!NOTE]
> The internal Vite plugin calls `getConfig` with the current working directory to dynamically resolve configuration options during the build process.

### Revalidable Content Source Resolution

Content sources within the preview environment are wrapped in a revalidable caching layer that manages dynamic application configurations. The `getSource` function utilizes a revalidable wrapper to instantiate a Fumadocs loader with Content sources fetched via `getPages`, incorporating the `lucideIconsPlugin` and setting the root `baseUrl` to `/`.

```typescript
import { loader, source } from 'fumadocs-core/source';
import { revalidable } from '@/lib/revalidable';
import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons';
import { getPages } from './storage';
import type { ParsedAppConfig } from '@/config/global';

export const getSource = revalidable({
  async create(config: ParsedAppConfig) {
    return loader({
      source: source(await getPages(config.content)),
      plugins: [lucideIconsPlugin()],
      baseUrl: '/',
    });
  },
});
```
Sources: [packages/preview/src/lib/source/index.ts:1-15](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/index.ts#L1-L15)

Type definitions export the inferred `Source` and `SourcePage` types alongside a helper function `getPageImage` that appends `image.webp` to slug segments for dynamic Open Graph image generation[Preview Server](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/preview-server).

```typescript
export type Source = Awaited<ReturnType<typeof getSource>>;
export type SourcePage = Source['$inferPage'];

export function getPageImage(slugs: string[]) {
  const segments = [...slugs, 'image.webp'];

  return {
    segments,
    url: `/og/${segments.join('/')}`,
  };
}
```
Sources: [packages/preview/src/lib/source/index.ts:17-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/index.ts#L17-L27)

> [!TIP]
> Open Graph image generation paths are constructed automatically by joining requested slug segments with `image.webp` under the `/og/` route prefix.

## Related

- [Bundler Plugins](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/mdx-pipeline/bundler-plugins)
- [Preview Server](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/preview-server)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/llms.txt) for all pages in this wiki.
