Skip to main content
doc0blade47/fumadocsdocumentation home
Sign in

Vite Integration Plugins

Last updated Oct 12, 2026100% grounded
Relevant source files

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

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

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

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.

"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

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.

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

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

Script NameCommandPurpose
buildtsdownCompiles the package using tsdown
cleanrimraf distRemoves the dist output directory
devtsdown --watch --clean falseRuns tsdown in watch mode without cleaning
lintoxlint .Lints the codebase using oxlint
types:checktsc --noEmitVerifies TypeScript types without emitting output

Sources: packages/vite/package.json:1-29

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

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

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.

 
  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

Property PathSource Object KeyTarget Output FilePurpose
optimizeDepsout.optimizeDepssrc/generated.jsonConfigures Vite dependency pre-bundling optimizations
ssr.noExternalout.ssr.noExternalsrc/generated.jsonForces specified framework packages to be bundled during SSR

Sources: packages/vite/tsdown.config.ts:14-21

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 NameTypeDefault ValuePurpose
tsconfigPathstring"tsconfig.json"Path to the TypeScript project configuration file
filterRegExp`/.story.(jsjsx

Sources: packages/story/src/vite/index.ts:7-21

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

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.

  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

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.

projectPromise ??= createControlsProject(tsconfigPath);
 
return transformStoryFile('@fumadocs/story/vite/client', code, id, await projectPromise);

Sources: packages/story/src/vite/index.ts:37-39

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 TypeStructure / FieldsPurpose
'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

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

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

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 NameTarget Runtime / Context
FD_LOCAL_MD_DEV_SERVER_URLStandard Node.js environment
NEXT_PUBLIC_FD_LOCAL_MD_DEV_SERVER_URLNext.js client/server bundles
VITE_FD_LOCAL_MD_DEV_SERVER_URLVite build configurations

Sources: packages/local-md/src/dev/shared.ts:51-55

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

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

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.

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

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.

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

Preview Server Bundler Wiring

Waku Configuration and Vite Plugin Integration

The built-in Preview Server relies on a Waku configuration file that sets up distribution directories and injects Tailwind CSS along with Fumadocs Vite pluginsPreview Server. The defineConfig utility from Waku specifies dist/waku as the output distribution directory and configures the underlying Vite bundler with tsconfigPaths resolution enabled.

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

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 /.

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

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 generationPreview Server.

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

Tip

Open Graph image generation paths are constructed automatically by joining requested slug segments with image.webp under the /og/ route prefix.

Was this page helpful?