首页
/ Immich Web 前端项目深度解析:基于 SvelteKit 的构建、开发代理与 SPA 部署机制

Immich Web 前端项目深度解析:基于 SvelteKit 的构建、开发代理与 SPA 部署机制

2026-09-06 13:03:34作者:钟日瑜

Immich 的 web/ 目录承载了整个系统的 Web 界面,它基于 SvelteKit 框架开发:本地开发时以 SvelteKit 的 Vite Node.js 开发服务器形态运行,生产部署时则被构建为 SPA(单页应用)产物,随 server 镜像一起分发并由后端托管静态资源。本文以 web/README.md 为主线,结合 svelte.config.jsvite.config.tsweb/package.jsonserver/Dockerfile,完整讲清该前端项目的技术栈、开发链路(dev server + 后端代理)、版本注入、生产构建流程,以及构建产物如何被 server 容器消费,帮助开发者在动手贡献代码前建立对这套前端工程的完整认知。

项目定位与技术栈选型

web/README.md 开篇即明确了三件核心事实:

  1. 项目使用 SvelteKit Web 框架(README 原文如此,读者可参见 SvelteKit 官方文档入门);
  2. 理解 SvelteKit 的文件路由(file-based routing) 是读懂本仓库 Web 代码的前提——src/routes/ 下的目录结构直接对应 URL 路径;
  3. 开发态与生产态形态不同:本地开发运行的是 SvelteKit Node.js 开发服务器,而生产环境是构建为 SPA 后并入 server 项目一起部署。

web/package.json 可以看到实际锁定的技术栈版本(以当前仓库为准):

依赖 版本 作用
@sveltejs/kit ^2.56.1 SvelteKit 框架本体
svelte 5.56.9 Svelte 5(Runes 时代的响应式语法)
@sveltejs/vite-plugin-svelte 7.3.0 SvelteKit 依赖的 Vite 插件
vite ^8.0.0 开发服务器与构建工具
@sveltejs/adapter-static ^3.0.8 静态站点适配器,生产 SPA 的关键
tailwindcss / @tailwindcss/vite ^4.2.4 Tailwind CSS v4(Vite 插件方式接入)
vitest / happy-dom / @testing-library/svelte 单元测试三件套
@immich/sdk workspace:* 与 server 共享的类型化 API 客户端,pnpm workspace 内部依赖
maplibre-gl / pmtiles / svelte-maplibre 地图与瓦片支持
hls.js / media-chrome 视频播放
svelte-i18n ^4.0.1 配合仓库根目录 i18n/ 下的 100+ 语言文件做多语言

其中两个值得注意的细节:

  • @immich/sdk: workspace:*:Web 前端并非手写 REST 调用,而是消费仓库内 packages/sdk 的本地包,接口类型由 OpenAPI 规格生成(参见 open-api/immich-openapi-specs.json),保证前后端类型一致;
  • i18n 别名svelte.config.js'$i18n': '../i18n' 将翻译文件目录直接映射为 $i18n 导入,而 server/Dockerfile 构建镜像时也显式 COPY ./i18n ./i18n/,说明国际化资源是 Web 构建的必需输入。

目录结构:以 SvelteKit 文件路由为骨架

理解 SvelteKit 文件路由后,web/ 的代码组织就很直观了。顶层目录:

web/
├── bin/immich-web        # 开发容器入口脚本(等待后端就绪后启动 dev server)
├── src/
│   ├── lib/              # 共享库代码(别名 $lib,约 470 个文件)
│   ├── routes/           # SvelteKit 文件路由:目录即 URL(160+ 个 .svelte 页面)
│   ├── params/           # URL 参数校验器
│   ├── service-worker/   # 离线/Service Worker 相关
│   ├── test-data/        # 测试夹具(别名 @test-data)
│   ├── app.css / app.html / app.d.ts
│   └── hooks.client.ts / hooks.server.ts   # 客户端/服务端生命周期钩子
├── static/               # 原样拷贝的静态资源(favicon、PWA manifest 等)
├── tests/                # 共享测试辅助(别名 $tests)
├── eslint.config.js
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts

几个关键别名在 svelte.config.js 中定义:

alias: {
  $lib: 'src/lib',
  '$lib/*': 'src/lib/*',
  $tests: 'src/../tests',
  '@test-data': 'src/test-data',
  $i18n: '../i18n',
}

$lib 是 SvelteKit 约定俗成的库代码别名;而 $i18n 指到仓库根目录的 i18n/ 文件夹——这也是为什么构建 web 时 i18n 目录必须与 web 目录同处一个构建上下文中。

本地开发:dev server、后端代理与 mise 任务

开发服务器与三个代理前缀

生产环境下 Web 界面与 server 同域同端口(2283)部署,因此前端所有请求都以 /api/.well-known/immich 等相对路径发出。本地开发时前后端分离(Vite 在 3000 端口、server 在 2283 端口),vite.config.ts 通过 Vite 的 dev proxy 把这三种前缀转发到后端:

const upstream = {
  target: process.env.IMMICH_SERVER_URL || 'http://immich-server:2283/',
  secure: true,
  changeOrigin: true,
  logLevel: 'info',
  ws: true,          // 支持 WebSocket,供 socket.io 实时通知使用
};

const proxy = {
  '/api': upstream,                  // 全部 REST / socket.io API
  '/.well-known/immich': upstream,   // 实例发现端点(App 配对用)
  '/custom.css': upstream,            // 自定义 CSS 注入
};

要点:

  • IMMICH_SERVER_URL 环境变量决定上游地址。默认值 http://immich-server:2283/ 面向 Docker 内部网络;本地裸跑时通常指向 http://localhost:2283
  • ws: true 使 WebSocket 升级请求也走代理,这是 Web 端实时推送(socket.io-client 在 web/package.json 中依赖)能工作的原因;
  • 该 proxy 配置同时作用于 serverpreview 两个环节(vite.config.ts),即 vite preview 静态预览产物时也保持同样的转发行为。

vite 配置里还有两处开发体验相关设置:server.allowedHosts: true(允许通过非 localhost 域名访问,适配远程开发场景)和 optimizeDeps.entries: ['src/**/*.{svelte,ts,html}'](显式声明预构建扫描入口)。

开发脚本

web/package.json 中的脚本一览:

"dev": "vite dev --host 0.0.0.0 --port 3000",   // 本地开发服务器
"build": "vite build",                          // 生产构建(SPA 静态产物)
"build:stats": "BUILD_STATS=true vite build",   // 额外产出 bundle 体积分析 stats.html
"preview": "vite preview",                      // 本地预览构建产物(带 proxy)
"check:svelte": "svelte-check --no-tsconfig --fail-on-warnings ...",
"check:typescript": "tsc --noEmit",
"lint": "eslint . --max-warnings 0 --concurrency 6",
"test": "vitest",
"prepare": "svelte-kit sync"                    // 安装后自动生成 .svelte-kit 类型/路由信息

build:stats 背后的机制在 vite.config.ts:当环境变量 BUILD_STATS=true 时动态挂载 rollup-plugin-visualizer,构建后输出可视化产物 stats.html,用于分析打包体积构成。

用 mise 一条命令拉起开发环境

仓库统一用 mise 管理工具链与任务。web/mise.toml 定义的任务链很有代表性:

[tasks.start]
run = [
  { task = ":install" },        # pnpm install --filter immich-web --frozen-lockfile
  { task = "//:sdk:install" },  # 安装并 …
  { task = "//:sdk:build" },    # 先构建 workspace 内的 @immich/sdk
  "pnpm run dev",
]

也就是说,web 单独开发的前置条件是先构建 @immich/sdk(因为它是 workspace:* 依赖),这正是 server/Dockerfilesdk 构建阶段先于 web 阶段的镜像原因——Web 构建在 FROM sdk AS web 阶段之上复用 SDK 产物。

另一个实用任务是 start-demo:注入 IMMICH_SERVER_URL = "https://demo.immich.app" 后执行 start,即可跳过本地后端、直接代理到官方演示实例做纯前端开发。

Docker 开发模式:bin/immich-web 等待后端就绪

除了裸跑,仓库还提供容器化开发。docker/docker-compose.dev.yml 中定义了 immich-web 服务(镜像 immich-web-dev:latest,命令 immich-web),其入口脚本即 web/bin/immich-web

cd /usr/src/app || exit
pnpm --filter @immich/sdk build

UPSTREAM="${IMMICH_SERVER_URL:-http://immich-server:2283/}"
# 轮询 GET ${UPSTREAM}/api/server/config 直到后端就绪
until wget --spider --quiet "${UPSTREAM}/api/server/config" > /dev/null 2>&1; do
  ...
  sleep 1
done
pnpm --filter immich-web exec vite dev --host 0.0.0.0 --port 3000

这个脚本值得注意:开发容器里跑的同样是 vite dev(热更新),而非构建产物;它先构建 SDK,再通过 /api/server/config 端点探活后端(每 10 次轮询打印一次等待日志),确保 API 代理不会把请求打到尚未就绪的 server。这也呼应了 vite.config.ts 中那句注释:"connect to a remote backend during web-only development"——该模式既支持代理本地后端,也支持通过 IMMICH_SERVER_URL 指向远端后端。

构建产物如何成为 SPA:adapter-static 与回退路由

web/README.md 最后一句"it is built as a SPA",其技术实现在 svelte.config.js

kit: {
  version: {
    name: process.env.IMMICH_BUILD || process.env.npm_package_version || 'local',
  },
  paths: { relative: false },
  adapter: adapter({
    fallback: 'index.html',
    precompress: true,
  }),
  ...
}

逐项解读:

  • @sveltejs/adapter-static + fallback: 'index.html':SvelteKit 构建出纯静态文件(无 Node 运行时),所有未被具体页面文件匹配的 URL 一律回退到 index.html,由前端路由接管——这就是 SPA 的标准落地方式。没有这一项,深链(deep link)直接访问会 404;
  • precompress: true:构建时预生成 gzip/brotli 压缩版本,静态托管时可直接下发压缩内容;
  • paths.relative: false:资源 URL 使用绝对路径(/_app/...),与 SPA 从任意路径被静态服务器兜底服务的要求一致;
  • version.name:构建版本号取自 IMMICH_BUILD(CI 构建 ID)→ 回退到 npm_package_version(即 package.json 的 3.2.0-rc.0)→ 本地开发则为 local。该版本信息会被 SvelteKit 写入应用运行时,供前端展示"当前运行的 Immich 版本",与 server 侧健康检查/状态页对版本的要求保持一致。server/DockerfileARG BUILD_ID / ENV IMMICH_BUILD=${BUILD_ID} 正是这条链路的上游。

版本构建产物的目录约定:build//build/www

vite build 的默认输出目录是 web/build。这一点在 server/Dockerfile 中得到了消费侧印证:

COPY --from=web /usr/src/app/web/build /build/www

而 server 端在 server/src/repositories/config.repository.ts 里定义了静态目录约定:

const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
const folders = {
  geodata: join(buildFolder, 'geodata'),
  web: join(buildFolder, 'www'),
};

并在 config.repository.ts 将其注册为静态资源配置(root: folders.webindexHtml: join(folders.web, 'index.html'))。由此形成完整的部署闭环:Dockerfile 把 web/build 拷到 /build/www,server 启动时从 IMMICH_BUILD_DATA(默认 /build)下的 www 子目录提供 Web 界面,与 docker/docker-compose.prod.yml 中 server 容器 2283 端口对外同时暴露 API 与页面的行为一致。

测试与质量保障

Web 项目的质量门禁同样由 web/package.jsonweb/mise.toml 组织:

  • 单元测试:Vitest + happy-dom 环境,配置内联在 vite.config.tsinclude: src/**/*.{test,spec}.{js,ts}TZ: 'UTC' 固定时区避免日期断言漂移、setup 文件 src/test-data/setup.ts);
  • 类型与编译检查tsc --noEmitsvelte-check --fail-on-warnings 双检查(check:code 还会串联 prettier 与 eslint);
  • mise 任务 ci-unit:SDK 安装构建 → install → format → check → test --run 的完整链路,checklist 任务在其上追加 lint,与 docs/developer/pr-checklist.md 描述的提交前检查项对应。

针对 Svelte 5 的迁移期,svelte.config.js 中留有 // TODO pending '@immich/ui' to enable it / runes: true 注释,onwarn 钩子与 check:svelte 脚本则统一忽略 state_referenced_locally 告警——说明当前代码库正处于 Runes 模式全面开启前的过渡状态,阅读源码时同一组件中混用 $state 与传统 store 是预期现象。

小结

回到 web/README.md 的三条主线,本文已逐一落到可验证的仓库证据上:SvelteKit 文件路由决定了 src/routes/ 的代码组织方式;vite dev --host 0.0.0.0 --port 3000 配合 IMMICH_SERVER_URL 代理(/api/.well-known/immich/custom.css,含 WebSocket)构成开发态,mise 的 :start 任务负责 SDK 前置构建与拉起;生产态则通过 adapter-staticfallback: 'index.html' 构建出 SPA,IMMICH_BUILD 注入版本号,vite build 产物经 server/Dockerfile 落入 /build/www,由 server 依 config.repository.ts 的约定静态托管。掌握这条从源码到容器的完整链路,即可独立启动 Immich Web 前端开发环境并理解其构建与部署行为。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395