故障排查
每个条目先描述你看到的现象,再说明原因和修复方法。
Svelte:onMount 从不执行,或 mount() 抛出 lifecycle_function_unavailable
现象。 Svelte island 能渲染,但 onMount 回调从不触发,或者 Svelte 5 的 mount() 抛出 lifecycle_function_unavailable。
原因。 svelte 在同一个导入路径下同时提供浏览器运行时和服务端运行时。你的客户端 bundle 选中了服务端那个。有些 SSR 环境(Vinxi、自定义 Vite environment、jsdom 中的 Vitest)在解析客户端代码时没有使用 browser 导出条件,于是 Vite 的依赖优化器预构建了服务端运行时。在浏览器开发者工具中,你会看到 svelte/src/index-server.js(Svelte 5)或 svelte/src/runtime/ssr.js(Svelte 4),而不是客户端文件。
修复。 把 browser 加回客户端的 resolve conditions,然后用 --force 重启 Vite,让它重新构建优化过的依赖:
// vite.config.ts (the client build or client environment)
import { defaultClientConditions, defineConfig } from 'vite';
export default defineConfig({
resolve: { conditions: ['browser', ...defaultClientConditions] },
});对于使用 jsdom 的 Vitest,添加该条件并内联 Svelte,让 Vite 来解析它:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
resolve: { conditions: ['browser'] },
test: { environment: 'jsdom', server: { deps: { inline: [/svelte/] } } },
});不要把 svelte 从 optimizeDeps 中排除;否则 esbuild 会报错 "The entry point "svelte" cannot be marked as external"。
Vue:~ 别名在 .vue 文件中失效
现象。 生产构建失败,报错 Rollup failed to resolve import "~/utils/x" from ".../Card.vue?vue&type=script...",而同样的导入在 .ts 文件中可以工作,.vue 文件中从 ~/ 进行的 import type 也可以工作。
原因。 vite-tsconfig-paths 只对其 tsconfig include 的文件应用别名。只包含 **/*.ts 和 **/*.tsx 的 tsconfig 会漏掉 .vue 文件。仅类型的导入在解析之前就被擦除,所以它们从不失败。
修复。 在该 tsconfig 中包含 .vue 文件:
{
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}或者直接在 Vite 中声明别名:
// vite.config.ts
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
export default defineConfig({
resolve: { alias: { '~': fileURLToPath(new URL('./src', import.meta.url)) } },
});请使用 Vue - Official(Volar)编辑器扩展。Vetur 不理解 <script setup> 宏和 moduleResolution: "Bundler"。
Angular 15-17:点击后视图不更新
现象。 Angular island 能渲染,但按钮处理函数修改了字段,模板却没有更新。
原因。 适配器在没有 zone.js 的情况下运行 Angular。Angular 18+ 在 zoneless 模式下会自行调度变更检测;在 15-17 上,适配器用一个空操作的 zone 替换 NgZone,DOM 事件之后没有任何东西运行变更检测。
修复。 在修改之后调用 detectChanges():
import { ChangeDetectorRef, Component, inject } from '@angular/core';
@Component({
selector: 'app-like',
standalone: true,
template: `<button (click)="like()">{{ likes }}</button>`,
})
export class LikeComponent {
private readonly cdr = inject(ChangeDetectorRef);
likes = 0;
like(): void {
this.likes += 1;
this.cdr.detectChanges();
}
}或者在客户端入口顶部导入 zone.js,并向 createAngularMicroApp 传入 zone: true。不要在服务端使用 zone.js:它会修改全局 Promise,导致服务端的 fetch 出错。
日期导致的水合不匹配
现象。 React 报告错误 #418(或 Vue 和 Svelte 输出水合不匹配日志),但只出现在部分用户身上,或只在午夜前后出现。差异显示日期相差一天,例如 21 Mart 2026 对比 22 Mart 2026。
原因。 服务端按自己的时区(通常是 UTC)格式化日期,浏览器按用户的时区格式化。不带 timeZone 的 new Intl.DateTimeFormat('tr-TR', { day: 'numeric', month: 'long' }) 在两端会得到不同的文本。
修复。 在所有由服务端渲染的日期格式化处传入明确的 timeZone:
const format = new Intl.DateTimeFormat('tr-TR', {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'Europe/Istanbul',
});
console.log(format.format(new Date('2026-03-21T22:30:00Z'))); // 服务端和客户端都是 22 Mart 2026toLocaleString、相对时间("3 hours ago")以及渲染期间使用 Date.now() 的任何代码同样适用。
一个请求的数据出现在另一个请求中
现象。 服务端渲染的 island 显示过期数据,或另一个用户请求的数据,随后客户端用不同的内容重新渲染。
原因。 模块级缓存,例如被多个微应用导入的 export const queryClient = new QueryClient(),会在整个服务端进程的生命周期中存在。每个请求都读写同一个实例。
修复。 在服务端为每个请求创建缓存,并把 island 需要的数据作为 props 传入。只在浏览器代码中保留一个模块级客户端。
import { QueryClient } from '@tanstack/query-core';
export function createRequestQueryClient(): QueryClient {
return new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } });
}参见按请求隔离的状态。
模块脚本应用在卸载后无法加载
现象。 在 orchestrator.unregister(name) 或 loader.unload(name) 之后,再次注册并挂载同一个应用会失败,报错 Module "<name>" loaded but no exports found。
原因。 浏览器对每个 URL 只运行一次模块脚本。针对同一 URL 的第二个 <script type="module" src="..."> 不做任何事,因此应用不会再次调用 registerModule。
修复。 使用加载器函数形式的入口,它会把模块保存在打包工具的模块缓存中;或者修改 URL(例如加上版本查询字符串):
import { createOrchestrator } from 'tuvix.js';
const orchestrator = createOrchestrator();
orchestrator.register({
name: 'editor',
entry: () => import('./apps/editor'),
container: '#editor',
});"loaded but no exports found"
现象。 [Tuvix Loader] Module "dashboard" loaded but no exports found.
原因和修复,按检查顺序排列:
- bundle 注册的名称与注册时使用的名称不同。
createReactMicroApp({ name: 'Dashboard' })和register({ name: 'dashboard' })不匹配。请使用同一个字符串。 - ES 模块 bundle 被当作经典脚本加载。Vite 的 ES 输出以
.js结尾;请在入口中加上type: 'module'。 - bundle 导出了模块,但从未注册它。请使用适配器或
defineMicroApp,或者自己调用registerModule(name, module)。
找不到容器
现象。 [Tuvix] Container "#app" not found in DOM.
原因。 编排器在挂载应用时查找容器。外部路由器的路由组件可能还没有渲染它。
修复。 把容器放在导航之前就已存在的布局中,或者在路由组件内用 TuvixIsland 承载该应用。参见外部路由器。
首次绘制没有样式
现象。 禁用 JavaScript 时,或在慢速连接下的短暂时间内,服务端渲染的 island 显示时没有 CSS。
原因。 Vite 通过 JavaScript 注入 CSS Modules、Vue <style> 和 Svelte 样式。服务端 HTML 没有链接其中任何一个。
修复。 在 Vite 配置中添加 @tuvix.js/vite 的 tuvix(),并把 renderAssetTags(await getAssets([...])) 输出到 <head> 中。参见 SSR Islands。