IntersectionObserver致SvelteKit预渲染失效,GHP动态路由404
问题分析
你的GitHub Pages静态站点中,/news/[newsId]动态路由出现以下问题:
- 从
/news页面点击链接进入/news/123正常 - 直接访问
/news/123返回Svelte客户端404
根源在于预渲染阶段内容未被正确生成:
预渲染时onMount不会执行,你的IntersectionObserver组件导致:
isMounted初始为false,slot内容完全不渲染intersecting初始为false,即使slot渲染,新闻内容也会被{#if intersecting}隐藏
Svelte Kit无法检测到动态路由下的实际内容,仅生成[newsId].html模板,而GitHub Pages作为静态主机无法将/news/123映射到模板文件,直接访问时返回fallback的index.html,客户端路由匹配失败显示404。
修改IntersectionObserver组件的关键点
1. 调整初始变量,保证预渲染时内容可见
修改组件内的变量初始化逻辑,让预渲染阶段默认显示内容:
<script lang="ts"> import { onMount } from 'svelte'; export let once = false; export let top = 0; export let bottom = 0; export let left = 0; export let right = 0; // 初始设为true,预渲染时直接显示内容 let intersecting = true; let container: HTMLElement | null = null; // 初始设为true,预渲染时直接渲染slot let isMounted = true; onMount(() => { // 客户端挂载后重置状态,恢复监听逻辑 intersecting = false; // --- 原有的IntersectionObserver和scroll监听代码保持不变 --- if (!!window && typeof window !== 'undefined' && typeof IntersectionObserver !== 'undefined') { const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`; const observer = new IntersectionObserver( (entries) => { intersecting = entries[0].isIntersecting; if (intersecting && once && container) { observer.unobserve(container); } }, { rootMargin } ); if (container) observer.observe(container); return () => (container ? observer.unobserve(container) : undefined); } const handler = () => { if (!container) return; const bcr = container.getBoundingClientRect(); intersecting = bcr.bottom + bottom > 0 && bcr.right + right > 0 && bcr.top - top < window.innerHeight && bcr.left - left < window.innerWidth; if (intersecting && once) { window.removeEventListener('scroll', handler); } }; window.addEventListener('scroll', handler); return () => window.removeEventListener('scroll', handler); }); </script>
2. 移除slot的渲染限制
原组件中{#if isMounted}的条件会阻止预渲染时的内容输出,直接改为:
<div bind:this={container}> <slot {intersecting} /> </div>
如果需要保留客户端挂载后的逻辑,也可以调整为:
<div bind:this={container}> {#if isMounted || typeof window === 'undefined'} <slot {intersecting} /> {/if} </div>
可选:强制预渲染所有新闻条目
在svelte.config.js中明确指定预渲染条目,确保所有新闻页面都被生成:
import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { toKebabCase } from './src/lib/utils'; // 引入你的转义函数 /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { adapter: adapter({ pages: 'build', assets: 'build', fallback: 'index.html', precompress: false, strict: true }), prerender: { entries: async () => { // 导入新闻数据,生成所有新闻的路由 const newsData = await import('./src/lib/data/news.json'); const newsEntries = newsData.default.map(item => `/news/${item.id}-${toKebabCase(item.title)}`); // 加上新闻列表页 return ['/news', ...newsEntries]; } } } }; export default config;
修改后,预渲染阶段会为每个新闻条目生成单独的静态HTML文件,GitHub Pages能直接响应/news/123的请求,客户端加载后IntersectionObserver仍能正常控制内容显示时机。
内容的提问来源于stack exchange,提问作者Juha Untinen
相关产品推荐
相关产品推荐

