perf(blog): 服务端化文章渲染并优化首屏加载 - #3
Merged
Merged
Conversation
- 文章渲染:将 Markdown、高亮和 slug 生成收口服务端,以交互岛保留复制、折叠及上传兼容行为。 - 首屏资源:按需加载设置与预览,延后视频预载,以 CSS 动画和缓存策略减少初始脚本与资源开销。 - 数据与文档:复用持久化标签 slug,流式加载评论,并记录性能基线与回归门禁。 - 验证:npm run lint(通过);npm run typecheck(通过);npm test(20 文件、72 测试通过);npm run build(通过);git diff --check(通过)。 - 行为边界:未修改数据库结构、既有 URL、Markdown 显示效果、图片存储协议或发布状态。
There was a problem hiding this comment.
Pull request overview
This PR reduces blog first-load cost by moving Markdown rendering + slug generation behind a server-only boundary, trimming client bundles (notably avoiding shipping the pinyin dictionary), and deferring non-critical UI (modals/video preload/animations) to improve TTI while keeping existing URLs stable.
Changes:
- Introduces server-only slug helpers and updates APIs/actions to generate slugs on the server; clients submit explicit slugs or empty values.
- Refactors Markdown rendering to be server-rendered (sanitized + highlighted HTML), retaining only small client “islands” for copy/collapse interactions and streaming comments with
Suspense. - Replaces/defers several client-side features (Framer Motion animations, settings/preview modals, background video preload) and adds caching headers + performance documentation/tests.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/slug.test.ts | Adds regression tests for server-only slug behavior and ensures pinyin stays server-side. |
| tests/post-repository.test.ts | Updates repository test fixture to include tagLinks. |
| tests/post-model.test.ts | Adds tests for persisted tag link mapping (name + slug). |
| tests/markdown.test.ts | Removes slug generation expectations from markdown helpers. |
| tests/markdown-server-renderer.test.tsx | Verifies server-rendered Markdown includes readable HTML, highlighting, and sanitization. |
| tests/markdown-renderer.test.tsx | Adds copy-to-clipboard regression test for code blocks. |
| tests/code-block-shell.test.ts | Updates tests to use extracted code language helpers module. |
| src/server/slug.ts | Introduces server-only slug generation and upload-slug resolution helpers. |
| src/server/repositories/posts.ts | Returns visible posts mapped with persisted tag links. |
| src/lib/markdown.ts | Removes pinyin slug helper from shared markdown utilities. |
| src/features/tags/actions.ts | Moves tag slug generation to server-only slug helper. |
| src/features/posts/model.ts | Adds tagLinks mapping helpers and combined mapping function. |
| src/features/posts/image-upload.ts | Adds articleTitle option and sends it to upload API. |
| src/features/posts/hooks/use-post-cover-upload.ts | Extends hook API to include articleTitle for server-side slug fallback. |
| src/features/posts/editor/source-markdown-editor.tsx | Passes articleTitle through editor image upload flow. |
| src/features/posts/editor/rich-editor.tsx | Passes articleTitle through rich editor image upload flow. |
| src/features/posts/editor/editor.tsx | Threads articleTitle through editor and upload actions. |
| src/features/posts/components/post-form.tsx | Removes client slug generation; lazily loads preview modal with preload-on-hover/focus. |
| src/features/posts/components/post-card.tsx | Replaces Framer Motion SVG border animation with CSS-based animation. |
| src/features/posts/components/markdown-renderer.tsx | Converts Markdown renderer to server-safe output + client copy controller + lighter code copy path. |
| src/features/posts/components/markdown-copy-controller.tsx | Introduces client-side selection copy handler without making whole renderer client-only. |
| src/features/posts/components/code-block-shell.tsx | Removes highlight.js auto-detection; adds copyText-based copy path. |
| src/features/posts/code-block-language.ts | Extracts code language parsing/detection logic into a lightweight module. |
| src/features/home/components/cinematic-hero.tsx | Defers background video preloading until idle after initial buffering. |
| src/components/layout/site-loader.tsx | Replaces loader animation with CSS transitions (removes Framer Motion dependency). |
| src/components/layout/navbar.tsx | Lazily loads settings modal with preload-on-hover/focus and conditional mounting. |
| src/app/template.tsx | Replaces page transition Framer Motion wrapper with CSS animation class. |
| src/app/post/[slug]/page.tsx | Uses persisted tag slugs; streams comments via Suspense instead of blocking render. |
| src/app/globals.css | Adds CSS animations for page enter + featured border, with reduced-motion support. |
| src/app/api/upload-image/route.ts | Resolves upload folder slug on server using explicit slug or title fallback. |
| src/app/api/posts/create/route.ts | Uses server-only slug helper for create route. |
| src/app/api/posts/[slug]/update/route.ts | Uses server-only slug helper for update route. |
| README.md | Updates performance architecture statement and links to new performance baseline doc. |
| next.config.ts | Adds cache headers for /videos and /images static assets. |
| docs/performance.md | Adds performance baseline, architectural constraints, and regression checklist. |
| docs/architecture.md | Links architecture doc to performance/boundary constraints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
101
to
111
| const handleCopy = async () => { | ||
| if (!onCopy) return | ||
| await onCopy() | ||
| if (onCopy) { | ||
| await onCopy() | ||
| } else if (copyText !== undefined) { | ||
| await navigator.clipboard.writeText(copyText) | ||
| } else { | ||
| return | ||
| } | ||
| setCopied(true) | ||
| window.setTimeout(() => setCopied(false), 2000) | ||
| } |
Comment on lines
16
to
33
| @@ -22,6 +31,15 @@ const nextConfig: NextConfig = { | |||
| }, | |||
| ], | |||
| }, | |||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
公开页面曾将 Markdown 解析、高亮和完整拼音字典带入客户端,同时全局动画、弹窗和首页视频也增加了首屏资源成本。问题通过生产构建分块审计和真实页面加载回归发现,文章详情客户端引用一度达到 8,533,843 B raw,影响下载、解析和交互就绪时间。
根因是服务端可完成的内容渲染跨越了 Client Component 边界,通用 Markdown 工具又静态引入
pinyin,导致所有调用日期和阅读时长工具的客户端页面携带约 6.39 MB raw 的词典分块。解决方案
将公开 Markdown 渲染和 slug 生成收口服务端,只保留复制、代码折叠等必要交互岛;同时拆分非首屏组件、延迟视频预载并用 CSS 动画替代全局 Framer Motion 依赖。选择继续使用现有拼音算法但限制为
server-only,以保持既有 URL 兼容。主要变更
Suspense流式加载。行为变化
验证
npm run lint:通过。npm run typecheck:通过。npm test:20 个测试文件、72 项测试全部通过。npm run build:Next.js 16.2.12 生产构建通过,24 个静态页面生成成功。git diff --check:通过。pinyin实现。npm run test:e2e和 Supabase 数据库测试;本次无数据库变更,PR CI 将运行对应检查后再合并。风险评估
pinyin算法,既有数据与 URL 不迁移;显式上传 slug 的优先级保持不变。发布与迁移
产物与证据
docs/performance.md。7075227 perf(blog): 服务端化文章渲染并优化首屏加载。后续工作
Reviewer 指引
src/server/slug.ts的server-only约束和上传 slug 回退优先级。变更类型
核对清单