fix(search): skip pages that fail to render instead of crashing dev server

Indexing runs unawaited from `configureServer`, so a page whose snippet or
include cannot be resolved rejected the scan and killed the process on
startup. Warn and skip the page instead; the error is still reported by the
markdown transform, and the build still fails on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pull/5307/merge
Divyansh Singh 2 days ago
parent e9b762b3d4
commit 3ffefa2550

@ -101,6 +101,49 @@ describe('node/plugins/localSearchPlugin', () => {
])
expect(zhIndex.search('rootonlytoken')).toEqual([])
})
test('warns and skips pages that fail to render', async () => {
root = await mkdtemp(path.join(tmpdir(), 'vitepress-local-search-'))
const configDir = path.join(root, '.vitepress')
await mkdir(configDir)
await writeFile(path.join(root, 'index.md'), '# Home\n\nhealthytoken\n')
await writeFile(
path.join(root, 'broken.md'),
'# Broken\n\n<!-- @include: ./missing.md -->\n'
)
await writeFile(
path.join(configDir, 'config.ts'),
"export default { themeConfig: { search: { provider: 'local' } } }"
)
const siteConfig = await resolveConfig(root, 'build', 'production')
const warn = vi
.spyOn(siteConfig.logger, 'warn')
.mockImplementation(() => {})
const plugin = await localSearchPlugin(siteConfig)
await (plugin.configResolved as any)?.call(
{},
{ publicDir: siteConfig.publicDir }
)
// the include throws, but indexing the remaining pages must still resolve —
// in dev this runs unawaited and a rejection would take the server down
await (plugin.load as any)?.handler.call({}, '/@localSearchIndex')
const index = loadIndex(
(await (plugin.load as any)?.handler.call(
{},
'/@localSearchIndexroot'
)) as string
)
expect(index.search('healthytoken')).toHaveLength(1)
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('Failed to index broken.md for search')
)
})
})
function loadIndex(serializedModule: string) {

@ -2,6 +2,7 @@ import { prefixRegex } from '@rolldown/pluginutils'
import MiniSearch from 'minisearch'
import path from 'node:path'
import { createDebug } from 'obug'
import c from 'picocolors'
import type { Plugin, ViteDevServer } from 'vite'
import type { SiteConfig } from '../config'
import type { DefaultTheme } from '../defaultTheme'
@ -132,7 +133,18 @@ export async function localSearchPlugin(
)
const index = getIndexByLocale(locale)
// retrieve file and split into "sections"
const html = await render(file)
let html: string
try {
html = await render(file)
} catch (e) {
// in dev this runs unawaited, so a page that fails to render must not
// reject and take the server down with it — the page reports the error
// itself through the markdown transform
siteConfig.logger.warn(
c.yellow(`Failed to index ${page} for search: ${(e as Error).message}`)
)
return
}
if (!html) return
const sections =
// user provided generator

Loading…
Cancel
Save