25 分钟
项目全解析

构建、部署与性能优化

就说从 npm run build 到生产服务器,完整走一遍构建流程,再讲性能优化的法子

  • 理解 Next.js 静态导出的配置
  • 掌握自定义静态服务器的实现
  • 就盯着性能优化的关键指标和手段学。
  • 了解 PWA 和离线缓存策略

构建流程:next build

Python
# Next.js 构建流程
print("""
1. TypeScript 类型检查(tsc --noEmit)
   → 零报错才能继续,strict 模式全开

2. 路由收集
   → 扫描 app/ 目录,识别所有页面
   → /course/[id] 是动态路由

3. 静态生成(SSG)
   → generateStaticParams() 返回所有课程 ID
   → 每个课程页预渲染为 HTML(318 个页面)

4. 代码分割
   → 每个页面独立 chunk
   → Monaco Editor 是最大的 chunk(~2MB)

5. 样式提取
   → Tailwind 只生成用到的 CSS(通常 < 20KB)

6. 输出到 out/ 目录
   → 纯静态文件,可部署到任何静态服务器
""")

next.config 配置

JavaScript
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "export",           // 静态导出
  images: { unoptimized: true },
  trailingSlash: true,
};

/*
 * 为什么用 output: "export"?
 * 1. 生成纯静态文件,双击 index.html 也能打开
 * 2. 可部署到 GitHub Pages、Nginx、任意 CDN
 * 3. 自带 server.js 提供更好的静态服务
 *
 * 代价:不能用 API Routes(但我们用 Pyodide,不需要后端)
 */

生产服务器:server.js

JavaScript
// server.js 核心功能
const http = require("http");
const fs = require("fs");
const zlib = require("zlib");

// 1. MIME 类型映射(.wasm, .apk 等)
// 2. gzip/brotli 压缩(减少 70% 传输体积)
// 3. Range 请求支持(大文件断点续传)
// 4. SPA 回退(找不到文件返回 index.html)
// 5. 安全头(X-Content-Type-Options, X-Frame-Options)

// 为什么不用 next start?
// next start 需要运行 Next.js 服务器,但我们是静态导出。
// 自定义 server.js 零依赖(只用 Node 内置模块),
// 支持 APK 下载、WASM 文件、局域网访问(手机扫码学习)。

性能优化策略

Python
# 朋友好学性能优化清单
print("""
首屏加载(LCP < 2.5s)
├── 静态预渲染:所有页面 SSG,HTML 直出
├── 字体优化:next/font 自托管,无 FOUT
├── CSS 内联:关键 CSS 在 <head> 中
└── 代码分割:Monaco/Pyodide 懒加载

运行时性能
├── Pyodide 在 Web Worker 中(不阻塞主线程)
├── React 并发渲染
├── CSS 变量主题切换(零 JS 开销)
└── requestAnimationFrame 节流动画

加载策略
├── Pyodide 懒加载(第一次运行代码时才加载)
├── Monaco Editor 动态 import
├── Service Worker 缓存已加载资源
├── gzip/brotli 压缩
└── HTTP 缓存头(immutable, max-age=31536000)
""")
🐍懒加载的艺术

Pyodide 是最大的资源,约10MB,但不是每个用户都会运行代码,所以在用户第一次点击「运行」时才加载。加载期间显示进度条。Monaco Editor 也用 next/dynamic 懒加载。就这么简单,把资源加载推迟到用户需要的那一刻。

PWA 与离线缓存

JavaScript
// Service Worker 缓存策略
// install: 预缓存核心资源
// fetch: 缓存优先,网络回退
self.addEventListener("fetch", (e) => {
  e.respondWith(
    caches.match(e.request).then((cached) => {
      if (cached) return cached;  // 缓存命中:离线可用
      return fetch(e.request).then((response) => {
        if (response.ok && e.request.method === "GET") {
          const clone = response.clone();
          caches.open(CACHE_NAME).then((cache) =>
            cache.put(e.request, clone)
          );
        }
        return response;
      });
    })
  );
});

/*
 * 缓存策略:
 * - HTML/导航:网络优先(确保更新)
 * - CSS/JS/字体:缓存优先(文件名带 hash,永久缓存)
 * - Pyodide WASM:缓存优先(文件大,不常变)
 */

一键启动脚本

Shell
:: start.bat
@echo off
cd /d "%~dp0"
if not exist "node_modules" ( call npm install )
if not exist "out" ( call npm run build )
start http://127.0.0.1:5173
node server.js
pause
选择题

朋友好学为什么用 output: 'export' 静态导出?

选择题

Pyodide(约 10MB)为什么要懒加载而不是首屏就加载?

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

千万别说「没什么难点」。按这条链讲:现象 → 你如何定位(日志、数据、复现)→ 根因 → 有哪些候选方案、各自代价 → 你最终怎么选、放弃了什么 → 上线后的量化收益。它展示的是解决未知问题的方法论,而这正是区分「会写代码」和「能扛事」的关键,也是高级岗面试的核心考点。

挑战任务

判断模块是否该懒加载

简单+50 XP

懒加载的判断标准:体积大、且不是首屏立即需要的资源才推迟加载。写 should_lazy(size_kb, needed_on_first_screen) 这个函数:体积超过500KB,且首屏不需要时返回True,否则返回False。

判断模块是否该懒加载
1 个测试用例