前端技术栈与实现(GitHub 文档翻译工具项目复盘)

前端技术栈的选择,本质是在交互复杂度与维护成本之间做取舍。项目要处理 GitHub 安装授权、文件树勾选、任务进度轮询这些交互,又得保证首屏快、代码好维护,还得让不认识我的人也能接手。对比过 Remix、SvelteKit,试过 Redux 又中途换掉,最后定下的组合是:Next.js 14 App Router 为主,TypeScript 强类型,shadcn/ui 提供组件,TanStack Query 管服务端状态,Zustand 管客户端状态,Tailwind 做样式。整体思路是”服务端优先、客户端按需水合”,把登录、列表等逻辑放服务端组件,把交互密集的部分放客户端组件。

一、技术选型对比

选型那两周我把每个候选都写了个 Demo,光对比记录就攒了好几屏。框架层面在 Next.js、Remix、SvelteKit 之间犹豫过,最终因为生态成熟度和 GitHub 相关库的兼容性选了 Next.js——Auth.js、TanStack Query 这些库对它的支持文档最全,遇到问题基本都能搜到答案。

维度 选项 本项目选择
框架 Next.js / Remix / SvelteKit Next.js(生态成熟)
路由 App Router / Pages App Router
组件库 shadcn/ui / MUI / Chakra shadcn/ui(轻、可改)
状态 Redux / Zustand / Recoil Zustand
服务端状态 SWR / TanStack Query TanStack Query
样式 Tailwind / CSS-in-JS Tailwind
鉴权 NextAuth.js v5 NextAuth.js v5
表单 React Hook Form / Formik React Hook Form + Zod

这套组合里几乎没有冷门选择,社区资料多,对开源项目长期维护很重要——后来 Contributors 提 PR 时基本不用先问”这个技术栈怎么用”。唯一被反复质疑的是 Zustand 而不是 Redux,理由在状态管理那节展开说。

二、整体架构

目录结构是从失败里长出来的。最初组件全堆在 /components,业务逻辑和 UI 混在一起,改一个功能要翻三四个文件,删一个功能还怕误删公共件。后来按 route、components、features、lib、hooks 分层,边界才清晰。

/app
  /(marketing)        公开页(首页、定价、文档)
  /dashboard          登录后页面
    /installations
    /repositories
    /jobs
  /api                服务端 API
/components           通用 UI
/features             业务组件(按功能分)
/lib                  工具与服务
/hooks                自定义 hooks

这个分层有个约定:/features 下每个功能自带组件、hooks 和 API 封装,/lib 只放无业务含义的工具函数,/hooks 放跨功能复用的自定义 hook。这样删一个功能直接删目录,不会留下僵尸代码,重构时也不用在整个仓库里翻找依赖关系。

三、关键页面实现

三个核心页面对应三类典型交互:选择、跟踪、配置。每个页面都坚持”服务端先出数据、客户端接管交互”的节奏,下面按页面拆开看。

3.1 仓库选择页

  • 服务端组件拉安装列表(Installation Token);
  • 客户端组件展示文件树;
  • 提交后调 POST /api/translation-jobs 创建任务。

文件树放在客户端,是因为它需要展开、勾选、半选这些本地状态;安装列表放在服务端,是因为它只读且每次都要鉴权。这个拆分让首屏 SSR 直接出列表,用户不用先看 loading 再等接口。

3.2 任务详情页

  • 顶部:进度条 + 状态卡片;
  • 中部:片段列表,每条显示状态、重试按钮;
  • 底部:日志折叠区。

任务页的轮询逻辑一开始写在组件里,切页就丢进度,回到页面还得重新等一轮。后来把轮询放到 TanStack Query 的 refetchInterval 配置里,配合窗口聚焦时重新拉取,体验稳定很多,代码也只剩几行配置。

3.3 设置页

  • 主题切换、API Key 管理、Webhook 配置;
  • 表单用 React Hook Form + Zod 校验。

设置页表单多,用 React Hook Form 管受控值,Zod 的 schema 前后端共用一份,前端做即时校验、后端做最终校验。字段加了约束不会出现一边改一边漏的情况——之前 Webhook 地址格式前后端校验不一致,白跑过几个坏请求。

四、状态管理分层

状态管理被 Redux 教育过一次就清醒了。项目里大部分数据来自服务端接口,真正的全局 UI 状态没几个,Redux 的样板代码带来的负担远超收益。四类状态的分工如下:

状态类型 工具 例子
服务端缓存 TanStack Query 仓库列表、任务列表
客户端 UI 状态 Zustand 主题、侧栏展开
表单 React Hook Form 创建任务表单
URL 状态 nuqs 搜索条件

这个分层让新同事接手时能很快判断”这个状态该放哪”,基本不需要讨论。TanStack Query 管服务端缓存天然支持过期失效和重试,Zustand 只存主题、侧栏这类本地偏好,表单状态留在表单组件里,搜索条件同步到 URL 方便分享链接。

五、服务端优先策略

App Router 的 RSC 直接改变了写页面的方式。列表数据用 Server Component 拉取,首屏不带 loading 状态,浏览器拿到的 HTML 里就有数据,SEO 和首屏速度都好,也不用自己设计一套数据加载态的骨架。

  • 列表数据用 Server Component 拉取,首屏不带 loading 状态;
  • 交互密集的页面用 "use client";
  • 表单提交用 Server Actions 减少 API 样板。

仓库选择页的服务端组件长这样,数据逻辑都在组件外,组件本身只剩数据流:

// app/dashboard/repositories/page.tsx
import { auth } from "@/lib/auth";
import { listInstallations } from "@/lib/github";

export default async function Page() {
  const session = await auth();
  const installations = await listInstallations(session!.user.id);
  return <RepoList initial={installations} />;
}

这里 auth() 和 listInstallations 都做了缓存,列表页刷新也不会重复打 GitHub API。踩过的坑是 “use client” 滥用:有一版文件树整页客户端渲染,首屏白屏一秒多,后来只把树组件标成客户端,其它保持服务端,速度立刻回来。

六、API 设计

REST 风格,统一前缀 /api,错误码用 HTTP 状态 + 自定义错误体。这个统一约定是前后端联调阶段踩了几次坑才固化下来的——前端总是拿到一堆字符串错误,没法判断该不该重试。统一错误结构如下:

type ApiError = {
  code: string;
  message: string;
  traceId: string;
};

把 traceId 加进错误体是被线上问题逼出来的——用户报错时只说”又失败了”,没有 traceId 根本查不到日志。现在前端捕获错误后把 traceId 显示出来,后端按 traceId 捞链路日志,定位快很多。WebSocket 暂未使用,任务进度走轮询,详情见姊妹篇。

七、可访问性

无障碍不是加分项,是默认项。做文件树时专门逐项核对过键盘操作,Tab 导航、方向键展开折叠、Enter 勾选,都按原生 tree 的 ARIA 模式实现,而不是只让鼠标能用。

  • 全部交互元素键盘可达;
  • 焦点环清晰;
  • 颜色对比满足 WCAG AA;
  • 模态使用 Radix UI,自带焦点陷阱。

Radix UI 的模态自带焦点陷阱,比手写弹窗省了很多心力。颜色对比这块靠 Tailwind 的语义色板兜底,自定义色值统一走主题变量,避免随手写一个接近背景色的文字。

八、国际化

文案用 next-intl,默认中文,提供英文 fallback。有一个坑:一开始直接拿翻译工具的输出当 UI 文案,结果界面中英夹杂,术语和界面文案混在一起,用户看设置页都不知道按钮是干什么的。

  • 文案用 next-intl;
  • 默认中文,提供英文 fallback;
  • 不把翻译工具的输出直接用于 UI 文案,避免术语冲突。

后来明确约定:UI 文案单独维护一份,翻译工具的译文只出现在任务结果里。这样界面语言稳定,也方便以后加别的语言时统一管理。

九、构建与性能

性能优化更多是”选对默认”。路由级代码分割、图片走 next/image、字体走 next/font 自我托管、Tailwind JIT 减小 CSS 体积,这些都是框架自带能力,把默认用对就有基础分。

  • 路由级代码分割;
  • 图片走 next/image;
  • 字体走 next/font 自我托管;
  • Tailwind JIT 减小 CSS 体积;
  • Lighthouse 性能 90+。

Lighthouse 90+ 是在做了资源预加载、减少客户端水合之后才稳定下来的。有一版把 markdown 渲染库在客户端全量引入,包体积涨到 400 多 KB,改成服务端渲染加动态加载后掉到 200 以内,首屏明显变快。

十、测试

测试策略按成本分档:逻辑核心用单元测试,关键用户路径用 e2e,视觉回归按需。文件树那种纯函数逻辑是最值得写单测的,边界条件多、容易回归;钩子逻辑反而难测,交给 e2e 兜底更划算。

  • 单元:Vitest + Testing Library;
  • e2e:Playwright 覆盖登录、创建任务、查看进度;
  • 视觉回归:Chromatic(按需)。

十一、CI

CI 就是质量的看门人。每次 PR 跑 lint、typecheck、test、build,Preview 部署用 Vercel 自动,合并保护要求 e2e 必须过。代价是每个 PR 要等几分钟,但换来的是 main 分支永远可部署,出问题也能定位到具体一次合并。

  • 每次 PR:lint、typecheck、test、build;
  • Preview 部署:Vercel 自动;
  • 合并保护:必须通过 e2e。

十二、可维护性

开源项目最怕个人风格浓。强类型 + 路径别名 @/*、业务组件按 feature 分目录、共享类型收敛到 packages/types、ESLint + Prettier + import order 统一,都是为了降低”该放哪、该叫啥”的决策成本。

  • 强类型 + 路径别名 @/*;
  • 业务组件按 feature 分目录;
  • 共享类型在 packages/types;
  • ESLint + Prettier + import order 统一。

这些约定写进 CONTRIBUTING 后,外部 PR 的代码风格基本一致,review 时间省了一大半。

十三、给同类项目的建议

如果让我重来一遍,会更早确定下面四条原则,而不是踩完坑再总结。它们不依赖具体框架,换技术栈也适用。

  1. 默认服务端组件,按需水合;
  2. 状态分层清晰,避免 Redux 滥用;
  3. 表单校验放 Zod,前后共用;
  4. 关键页 e2e 覆盖,其它靠单元测试。

技术栈本身不复杂,难的是把每层边界划清楚。这一套跑下来,前端从选型到稳定上线,返工最多的不是框架功能,而是状态归属和渲染边界这两件事,提前定好规则能省下大量迭代时间。

常见问题(FAQ)

Q1:为什么不用 Redux?

状态主要来自服务端,TanStack Query 更合适,Zustand 处理 UI 状态。

Q2:App Router 与 Pages Router 怎么选?

新项目选 App Router,老项目谨慎迁移。

Q3:样式方案用 CSS-in-JS 怎么样?

Next.js App Router 对 CSS-in-JS 支持在收敛,Tailwind 更稳。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部