Relevant source files
The following files were used as context for generating this wiki page:
- packages/preview/src/waku.server.tsx
- packages/preview/src/cli/commands.ts
- packages/preview/src/components/hot-reload.tsx
- packages/preview/src/pages/_api/og/...slugs/image.webp.tsx
- packages/preview/src/pages/...slugs.tsx
- packages/preview/src/components/ai/search.tsx
- packages/openapi/src/server/index.tsx
- packages/preview/waku.config.ts
- packages/preview/src/pages/_api/api/chat.ts
- packages/asyncapi/src/server/index.tsx
- packages/core/src/framework/waku.tsx
- packages/preview/src/pages/_root.tsx
- packages/preview/src/pages/_api/img.ts
- packages/preview/src/components/provider.tsx
- packages/local-md/src/dev/react-client.ts
- packages/mdx/src/runtime/dynamic.ts
- packages/local-md/src/dev/node-server.ts
- packages/base-ui/src/provider/waku.tsx
- packages/preview/src/lib/ai.ts
- packages/preview/src/lib/source/index.ts
- packages/mdx/src/runtime/server.ts
- packages/preview/src/pages/_api/api/search.ts
- packages/core/src/search/orama/create-server.ts
- packages/preview/package.json
- packages/preview/src/lib/env.d.ts
- packages/radix-ui/src/provider/waku.tsx
- packages/preview/src/cli/index.ts
- packages/preview/src/lib/source/watcher.ts
- packages/preview/fumadocs.config.ts
- packages/base-ui/src/og/takumi.tsx
Overview
The Fumadocs preview server provides an opinionated runtime environment built on Waku and Hono to instantly scaffold, render, and live-reload documentation projects [[packages/preview/package.json:4-4], [packages/preview/src/waku.server.tsx:1-38]]. By combining file-system routing with reactive source collection loaders, it resolves markdown and MDX files dynamically while serving specialized endpoints for full-text search, AI-assisted chat, and dynamic OpenGraph image generation [[packages/preview/src/pages/%5B...slugs%5D.tsx:76-96], [packages/preview/src/pages/_api/api/chat.ts:143-156], [packages/preview/src/pages/_api/api/search.ts:23-27], [packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:7-21]].
Sources: packages/preview/package.json:4-4, packages/preview/src/waku.server.tsx:1-38, packages/preview/src/pages/...slugs.tsx:76-96, packages/preview/src/pages/_api/api/chat.ts:143-156, packages/preview/src/pages/_api/api/search.ts:23-27, packages/preview/src/pages/_api/og/...slugs/image.webp.tsx:7-21
CLI Startup and Server Bootstrap
Command Initiation and Environment Configuration
The CLI entry point uses commander to register the default start command alongside an init utility command packages/preview/src/cli/index.ts:8-30. When executed, runStart loads environment variables and overrides NODE_ENV to production packages/preview/src/cli/commands.ts:16-17.
export async function runStart({
host,
dirs,
port: defaultPort = '8080',
}: {
dirs?: string[];
host?: string;
port?: string;
}) {
loadEnv();
overrideNodeEnv('production');
const port = await getFreePort(parseInt(defaultPort, 10));
const serveFileUrl = pathToFileURL(path.resolve(baseDir, 'dist', 'waku', 'serve-node.js')).href;
if (host) {
process.env.HOST = host;
}
process.env.PORT = String(port);
process.env.ROOT_DIR = process.cwd();
if (!dirs || dirs.length === 0) {
dirs = [''];
}
process.env.DEFAULT_PROJECT_DIR = JSON.stringify(dirs.map((v) => path.resolve(v)));
process.env.HOT_RELOAD = '1';
process.chdir(baseDir);
await import(serveFileUrl);
console.log(`ready: Listening on http://${host || 'localhost'}:${port}/`);
}Sources: packages/preview/src/cli/commands.ts:7-34
Port Resolution and Adapter Bootstrapping
Port allocation is managed by probing net.createServer incrementally starting from the default port (8080) until an available socket is found, ignoring non-in-use errors packages/preview/src/cli/commands.ts:36-52. The Waku Node adapter evaluates file-system router modules from src/pages and configures middleware for hot reloading packages/preview/src/waku.server.tsx:1-38.
import { createNodeWebSocket, NodeWebSocket } from '@hono/node-ws';
import adapter from 'waku/adapters/node';
import { fsRouter } from 'waku/router/server';
import { startWatcher } from './lib/source/watcher';
import { getConfigRuntime } from './config/load-runtime';
import { encodeEvent, WebSocketEvent } from './lib/waku/hot-reload';
import { filesCache } from './lib/source/storage';
import { serve } from '@hono/node-server';
import { getSource } from './lib/source';
const modules = import.meta.glob('./pages/**/*.{ts,tsx,js,jsx}', {
base: '/src',
});
const pages = fsRouter(modules);
let nodeWs: NodeWebSocket | undefined;
const v = adapter(pages, {
middlewareFns: [
({ app }) => {
if (process.env.HOT_RELOAD === '1') {
nodeWs = createNodeWebSocket({ app });
void initHotReload().catch((e) => {
console.error(e);
process.exit(1);
});
app.get(
'/_ws',
nodeWs.upgradeWebSocket(() => ({})),
);
}
return (_c, next) => next();
},
],
});Sources: packages/preview/src/waku.server.tsx:1-38
File Watching and Live Hot Reloading
Source Tree Watcher Initialization
The preview server initializes file system monitoring using Chokidar through startWatcher, which parses application configuration to normalize project directories packages/preview/src/lib/source/watcher.ts:8-9. It builds a set of ignored patterns by asynchronously reading .gitignore files from both the root working directory (falling back to node_modules, dist, and build) and individual project paths, utilizing the ignore package to validate and convert rules into Chokidar-compatible matcher functions packages/preview/src/lib/source/watcher.ts:10-20, packages/preview/src/lib/source/watcher.ts:29-48.
const watcher = watch([], {
ignoreInitial: true,
followSymlinks: false,
ignored: ignored.filter((v) => v !== undefined),
});
for (const project of projects) {
if (project.watch) watcher.add(project.dir);
}Sources: packages/preview/src/lib/source/watcher.ts:16-24
The Chokidar watcher is instantiated with specific options to skip initial addition events, disable symlink following, and filter paths against the compiled ignore rules packages/preview/src/lib/source/watcher.ts:16-20. It iterates over normalized projects and adds each project directory to the watch list if project.watch is enabled packages/preview/src/lib/source/watcher.ts:22-24.
Note
Initial file discovery events are ignored via ignoreInitial: true to prevent triggering redundant revalidation cycles during server boot.
Sources: packages/preview/src/lib/source/watcher.ts:16-17
WebSocket Event Propagation
When HOT_RELOAD is enabled (HOT_RELOAD === '1'), the server attaches a Node WebSocket instance via @hono/node-ws on the /_ws route packages/preview/src/waku.server.tsx:21-33. The initHotReload function coordinates watcher events with connected WebSocket clients by encoding messages through encodeEvent and broadcasting them across all active client sockets in wss.clients packages/preview/src/waku.server.tsx:56-64.
watcher.on('all', (event, filePath) => {
switch (event) {
case 'change':
case 'unlink':
// if file wasn't parsed, no revalidation is needed
if (!filesCache.has(filePath)) return;
}
if (event === 'change') {
filesCache.delete(filePath);
send({
type: 'clear-cache',
absolutePath: filePath,
});
}
switch (event) {
case 'add':
case 'change':
case 'unlink':
getSource.revalidate(false);
send({
type: 'revalidate',
});
break;
}
});Sources: packages/preview/src/waku.server.tsx:66-92
File system modifications trigger specific handling depending on the Chokidar event type:
| Event Type | Cache Action | Source Revalidation | WebSocket Payload Sent |
|---|---|---|---|
change | Deletes entry from filesCache if present | Triggers getSource.revalidate(false) | Clears cache path, then sends revalidate |
unlink | Bypasses cache eviction if unparsed | Triggers getSource.revalidate(false) | Sends revalidate |
add | None | Triggers getSource.revalidate(false) | Sends revalidate |
Sources: packages/preview/src/waku.server.tsx:66-92
Warning
If a changed or unlinked file is absent from filesCache (filesCache.has(filePath) is false), cache clearing is skipped to avoid unnecessary revalidation overhead for unparsed assets packages/preview/src/waku.server.tsx:70-72.
Sources: packages/preview/src/waku.server.tsx:70-72
Client-Side Page Re-Rendering
Client-side hot reloading is integrated into root element rendering via the HotReload component when HOT_RELOAD === '1' packages/preview/src/pages/_root.tsx:13. The client establishes a WebSocket connection to /_ws, dynamically switching between ws:// and wss:// protocols based on the current window location packages/preview/src/components/hot-reload.tsx:8-11.
useEffect(() => {
const url = new URL('/_ws', window.location.href);
url.protocol = url.protocol === 'https' ? 'wss' : 'ws';
const socket = new WebSocket(url);
socket.addEventListener('message', (event) => {
const decoded = decodeEvent(event.data);
if (!decoded) return;
console.log('Received:', decoded);
if (decoded.type === 'revalidate') {
void router.reload();
}
});
Sources: packages/preview/src/components/hot-reload.tsx:8-22
Upon receiving incoming messages, the browser runtime decodes the event payload using decodeEvent packages/preview/src/components/hot-reload.tsx:13-14. If the decoded event type matches revalidate, the client invokes router.reload() from Waku to fetch updated server content and re-render the page without a full browser refresh packages/preview/src/components/hot-reload.tsx:18-20.
Content Source Loading and Page Routing
Reactive Source Collection Loader
The content source collection is initialized through getSource, which wraps a revalidable factory function utilizing loader and source from fumadocs-core/source alongside lucideIconsPlugin() with a base URL of '/' packages/preview/src/lib/source/index.ts:7-15. This loader processes raw page records retrieved from file storage via getPages(config.content) based on the parsed global configuration packages/preview/src/lib/source/index.ts:4-10. Page image routes are constructed by appending 'image.webp' to the provided slug segments under the /og/ path prefix packages/preview/src/lib/source/index.ts:20-26.
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:7-15
Note
The source collection leverages revalidable wrapper semantics to ensure that file system modifications trigger cache invalidation without requiring a full application restart packages/preview/src/lib/source/index.ts:7.
Sources: packages/preview/src/lib/source/index.ts:7
Root Layouts and Provider Configuration
Root HTML document structure and client context wrapping are established in the root layout module packages/preview/src/pages/_root.tsx:7-17. The RootElement component renders an <html> element with language metadata and suppression of hydration warnings, containing a <body> tag configured with a minimum screen height and a version data attribute (data-version="1.0") packages/preview/src/pages/_root.tsx:7-11. Client-side UI context is provided by wrapping children in the Provider component, which embeds RootProvider from fumadocs-ui/provider/waku packages/preview/src/components/provider.tsx:5-7. When HOT_RELOAD === '1', the HotReload component is additionally injected into the document body packages/preview/src/pages/_root.tsx:13. Both the root layout and dynamic route components export a getConfig function defining a 'dynamic' render mode [[packages/preview/src/pages/_root.tsx:19-23], [packages/preview/src/pages/%5B...slugs%5D.tsx:225-228]].
export default async function RootElement({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head></head>
<body data-version="1.0" className="flex flex-col min-h-screen">
<Provider>{children}</Provider>
{process.env.HOT_RELOAD === '1' && <HotReload />}
</body>
</html>
);
}Sources: packages/preview/src/pages/_root.tsx:7-17
Dynamic Catch-All Route Rendering
Catch-all route rendering is managed by the DocPage component located at [...slugs].tsx, which retrieves runtime configuration and loads the content source packages/preview/src/pages/%5B...slugs%5D.tsx:76-79. Depending on the configured layout preset (config.layout.presets?.md), the page dynamically imports either the 'docs' or 'flux' layout and page component sets from fumadocs-ui/layouts packages/preview/src/pages/%5B...slugs%5D.tsx:80-93.
| Layout Preset | Layout Import Path | Page Component Import Path |
|---|---|---|
docs | fumadocs-ui/layouts/docs | fumadocs-ui/layouts/docs/page |
| Other (Flux) | fumadocs-ui/layouts/flux | fumadocs-ui/layouts/flux/page |
Sources: packages/preview/src/pages/...slugs.tsx:83-93
Markdown compilation is executed via a pre-configured markdown compiler instance incorporating remark and rehype plugins packages/preview/src/pages/%5B...slugs%5D.tsx:29-46.
const compiler = createMarkdownCompiler({
remarkPlugins: [
remarkGfm,
remarkMath,
remarkMdxMermaid,
plugin(remarkHeading, { generateToc: false }),
plugin(remarkNpm, { persist: { id: 'package-manager' } }),
remarkCodeTab,
],
remarkRehypeOptions: {
passThrough: ['mdxJsxFlowElement', 'mdxJsxTextElement'],
},
rehypePlugins: [
rehypeKatex,
plugin(rehypeCode, { lazy: true, fallbackLanguage: 'text' }),
plugin(rehypeToc, { exportToc: { as: 'data' } }),
],
});Sources: packages/preview/src/pages/...slugs.tsx:29-46
The remark and rehype plugin suite applied during compilation consists of the following processing modules:
| Plugin Category | Plugin Name | Configuration Options / Behavior |
|---|---|---|
| Remark | remarkGfm | GitHub Flavored Markdown support |
| Remark | remarkMath | Mathematical notation parsing |
| Remark | remarkMdxMermaid | Mermaid diagram embedding in MDX |
| Remark | remarkHeading | Heading processing (generateToc: false) |
| Remark | remarkNpm | Package manager tab persistence (persist: { id: 'package-manager' }) |
| Remark | remarkCodeTab | Code block tab grouping |
| Rehype | rehypeKatex | KaTeX math rendering |
| Rehype | rehypeCode | Syntax highlighting (lazy: true, fallbackLanguage: 'text') |
| Rehype | rehypeToc | Table of contents extraction (exportToc: { as: 'data' }) |
Sources: packages/preview/src/pages/...slugs.tsx:30-45
Warning
If compilation fails due to malformed syntax or missing references, the renderer catches the error and displays a fallback container rendering the exact error message and stack trace inside a code block packages/preview/src/pages/%5B...slugs%5D.tsx:170-194.
Sources: packages/preview/src/pages/...slugs.tsx:170-194
AI Assistant and Flexsearch Endpoints
Flexsearch Search Indexing Endpoint
The preview server exposes a dedicated search endpoint via GET(request) in packages/preview/src/pages/_api/api/search.ts that provides full-text document querying for the client search panel packages/preview/src/pages/_api/api/search.ts:23-28. It relies on a cached server instance created through the revalidable wrapper packages/preview/src/pages/_api/api/search.ts:7-21. The server compiles the content source using flexsearchFromSource, building index entries for every page packages/preview/src/pages/_api/api/search.ts:8-19.
return flexsearchFromSource(source, {
buildIndex(page) {
return {
id: page.absolutePath!,
structuredData: structure(page.data.content),
title: page.data.title,
description: page.data.description,
url: page.url,
};
},Sources: packages/preview/src/pages/_api/api/search.ts:9-18
Each indexed document extracts the absolute file path as its identifier, uses structure(page.data.content) to extract structural data, and maps the title, description, and target URL packages/preview/src/pages/_api/api/search.ts:10-17. The underlying server.GET(request) handler processes incoming query parameters to return matching documentation segments.
Sources: packages/preview/src/pages/_api/api/search.ts:23-27
AI Chat Endpoint and Model Routing
The server-side chat API located in packages/preview/src/pages/_api/api/chat.ts processes incoming user messages through the AI SDK streamText function packages/preview/src/pages/_api/api/chat.ts:129-138. Before executing completions, the handler validates AI support via isAISupported() and enforces IP-based rate limits if enabled in the runtime configuration packages/preview/src/pages/_api/api/chat.ts:110-125.
| Rate Limit Setting / Variable | Default Value / Behavior | Source Reference |
|---|---|---|
rateLimitWindowMs | 60 * 1000 (60 seconds) | packages/preview/src/pages/_api/api/chat.ts:55 |
rateLimitMaxRequests | 20 requests per window | packages/preview/src/pages/_api/api/chat.ts:56 |
| IP Headers Checked | x-forwarded-for, cf-connecting-ip, x-real-ip, x-client-ip | packages/preview/src/pages/_api/api/chat.ts:63-77 |
Sources: packages/preview/src/pages/_api/api/chat.ts:55-77, packages/preview/src/pages/_api/api/chat.ts:110-125
Warning
If a client exceeds rateLimitMaxRequests within the rateLimitWindowMs sliding window, the POST handler aborts immediately and returns a 429 Too Many Requests status alongside a Retry-After response header indicating the remaining window time in seconds.
Sources: packages/preview/src/pages/_api/api/chat.ts:98-103, packages/preview/src/pages/_api/api/chat.ts:118-124
Language models are initialized through defaultModel() in packages/preview/src/lib/ai.ts, which dynamically imports @openrouter/ai-sdk-provider and configures OpenRouter authentication using OPENROUTER_API_KEY and OPENROUTER_MODEL (defaulting to anthropic/claude-3.5-sonnet) packages/preview/src/lib/ai.ts:4-11.
export async function defaultModel(): Promise<LanguageModel> {
const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
return openrouter.chat(process.env.OPENROUTER_MODEL ?? 'anthropic/claude-3.5-sonnet');
}Sources: packages/preview/src/lib/ai.ts:4-11
Sources: packages/preview/src/lib/ai.ts:13-16
Server-Side Search Tool and Prompt Architecture
The chat completion stream equips the language model with a server-side search tool built using Flexsearch [[packages/preview/src/pages/_api/api/chat.ts:23-44], [packages/preview/src/pages/_api/api/chat.ts:143-156]]. The tool accepts a query string and an integer limit between 1 and 100 (defaulting to 10), executing searchAsync with index merging and enrichment enabled packages/preview/src/pages/_api/api/chat.ts:145-154.
const searchTool = tool({
description: 'Search the docs content and return raw JSON results.',
inputSchema: z.object({
query: z.string(),
limit: z.number().int().min(1).max(100).default(10),
}),
async execute({ query, limit }) {
const config = await getConfigRuntime();
const source = await getSource(config);
const search = createSearchServer(source);
return await search.searchAsync(query, { limit, merge: true, enrich: true });
},
});Sources: packages/preview/src/pages/_api/api/chat.ts:143-156
The system prompt instructs the assistant to invoke the search tool to fetch documentation context before formulating answers, ground its responses in raw JSON search payload results, and cite sources as markdown links using the document url field packages/preview/src/pages/_api/api/chat.ts:46-51. The completion runner limits execution steps to a maximum count of 5 via stepCountIs(5) and serializes responses through result.toUIMessageStreamResponse() packages/preview/src/pages/_api/api/chat.ts:129-141.
Sources: packages/preview/src/pages/_api/api/chat.ts:46-51, packages/preview/src/pages/_api/api/chat.ts:129-141
Client Search Panel and AI Chat State
The client-side UI components in packages/preview/src/components/ai/search.tsx manage the interactive chat drawer and message list [[packages/preview/src/components/ai/search.tsx:23-27], [packages/preview/src/components/ai/search.tsx:331-388]]. The AISearch wrapper component initializes the chat state using useChat from @ai-sdk/react, pointing its transport layer to /api/chat via DefaultChatTransport packages/preview/src/components/ai/search.tsx:292-299.
export function AISearch({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false);
const chat = useChat({
id: 'search',
transport: new DefaultChatTransport({
api: '/api/chat',
}),
});
Sources: packages/preview/src/components/ai/search.tsx:292-300
Keyboard shortcuts are handled by useHotKey(), which listens for Escape to close the panel when open, and Cmd+/ or Ctrl+/ to toggle the AI search panel open packages/preview/src/components/ai/search.tsx:420-439. The chat message list filters out system messages and displays empty state placeholders or rendered markdown message elements packages/preview/src/components/ai/search.tsx:390-417.
Sources: packages/preview/src/components/ai/search.tsx:390-417, packages/preview/src/components/ai/search.tsx:420-439
Dynamic OG Image and Media Handling
OpenGraph Image Generation
The preview server generates dynamic OpenGraph images using @takumi-rs/image-response through the dynamic catch-all route at packages/preview/src/pages/_api/og/[...slugs]/image.webp.tsx packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:5-21. The GET handler resolves runtime configuration via getConfigRuntime(), retrieves the content source via getSource(config), and looks up the page matching the requested slug parameters packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:8-9. If no page exists, the endpoint returns a 404 status response packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:11.
export async function GET(_: Request, { params }: ApiContext<'/og/[...slugs]/image.webp'>) {
const config = await getConfigRuntime();
const page = (await getSource(config)).getPage(params.slugs);
if (!page) return new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="Fumapress" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}Sources: packages/preview/src/pages/_api/og/...slugs/image.webp.tsx:7-21
When the page is found, ImageResponse renders the default layout component provided by fumadocs-ui/og/takumi with a width of 1200 pixels, a height of 630 pixels, and the WebP format packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:13-20. The underlying generator function from packages/base-ui/src/og/takumi.tsx configures a flex column container with a default background color of #0c0c0c, a padding of 4rem, and a primary accent bottom border packages/base-ui/src/og/takumi.tsx:55-65.
Sources: packages/base-ui/src/og/takumi.tsx:55-65
The configuration object exported by packages/preview/src/pages/_api/og/[...slugs]/image.webp.tsx sets render to dynamic packages/preview/src/pages/_api/og/%5B...slugs%5D/image.webp.tsx:23-26.
export async function getConfig() {
return {
render: 'dynamic' as const,
} as const;Sources: packages/preview/src/pages/_api/og/...slugs/image.webp.tsx:23-26
Runtime Image Asset Serving
The endpoint located at packages/preview/src/pages/_api/img.ts serves local media files referenced within documentation packages/preview/src/pages/_api/img.ts:19-66. The request URL search parameters supply src, page (the absolute path of the page), and project (the project directory identifier) packages/preview/src/pages/_api/img.ts:20-25. If any of these parameters are missing or not strings, the handler returns a 404 status response packages/preview/src/pages/_api/img.ts:27-29.
export async function GET(request: Request) {
const searchParams = new URL(request.url).searchParams;
const src = searchParams.get('src');
// absolute path of page
const pagePath = searchParams.get('page');
// project dir
const projectDir = searchParams.get('project');
if (typeof src !== 'string' || typeof pagePath !== 'string' || typeof projectDir !== 'string') {
return new Response(null, { status: 404 });
}
Sources: packages/preview/src/pages/_api/img.ts:19-30
The server validates the project directory against runtime configuration projects, returning a 400 status with invalid project directory if the project is unrecognized packages/preview/src/pages/_api/img.ts:31-34. It then constructs possible file paths combining the page directory, project asset directories, or fallback root directories defined by process.env.ROOT_DIR or process.cwd() packages/preview/src/pages/_api/img.ts:36-45.
const config = await getConfigRuntime();
const projects = normalizeProjects(config.content.projects);
const project = projects.find((item) => item.dir === projectDir);
if (!project) return new Response('invalid project directory', { status: 400 });
const possiblePaths: string[] = [path.join(path.dirname(pagePath), src)];
if (project.assetsDir) {
for (const dir of project.assetsDir) {
possiblePaths.push(path.join(dir, src));
}
} else {
const rootDir = process.env.ROOT_DIR ?? process.cwd();
possiblePaths.push(path.join(rootDir, src), path.join(rootDir, 'public', src));
}Sources: packages/preview/src/pages/_api/img.ts:31-45
Warning
File system traversal checks are skipped if content.unsafe_disableFileSystemChecks is enabled in configuration, allowing paths outside the project directory packages/preview/src/pages/_api/img.ts:47-49.
Supported image formats map specific file extensions to their respective MIME types as defined in supportedImageFormats packages/preview/src/pages/_api/img.ts:7-17.
| File Extension | MIME Type |
|---|---|
.png | image/png |
.jpg | image/jpeg |
.jpeg | image/jpeg |
.webp | image/webp |
.gif | image/gif |
.svg | image/svg+xml |
.avif | image/avif |
.bmp | image/bmp |
.ico | image/x-icon |
Sources: packages/preview/src/pages/_api/img.ts:7-17
When a matching file path with a supported extension is successfully read via fs.readFile, the handler responds with the file buffer and sets the Content-Type header to the corresponding MIME type packages/preview/src/pages/_api/img.ts:48-63. If all candidate paths fail, it returns a 404 status response packages/preview/src/pages/_api/img.ts:65.
for (const file of possiblePaths) {
if (!disableChecks && path.relative(project.dir, file).startsWith(`..${path.sep}`)) continue;
const mimeType = supportedImageFormats[path.extname(file)];
if (!mimeType) continue;
try {
return new Response(await fs.readFile(file), {
headers: {
'Content-Type': mimeType,
},
});
} catch {
continue;
}
}
return new Response(null, { status: 404 });
}Sources: packages/preview/src/pages/_api/img.ts:48-66
Related
On This Page
OverviewCLI Startup and Server BootstrapCommand Initiation and Environment ConfigurationPort Resolution and Adapter BootstrappingFile Watching and Live Hot ReloadingSource Tree Watcher InitializationWebSocket Event PropagationClient-Side Page Re-RenderingContent Source Loading and Page RoutingReactive Source Collection LoaderRoot Layouts and Provider ConfigurationDynamic Catch-All Route RenderingAI Assistant and Flexsearch EndpointsFlexsearch Search Indexing EndpointAI Chat Endpoint and Model RoutingServer-Side Search Tool and Prompt ArchitectureClient Search Panel and AI Chat StateDynamic OG Image and Media HandlingOpenGraph Image GenerationRuntime Image Asset ServingRelated