面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 06月3日 00:04

Docker 网络模式有哪些?bridge、host 和 overlay 怎么选?

Docker 有四种网络模式:bridge(默认)、host、none、overlay。日常开发用 bridge,性能敏感用 host,集群通信用 overlay。bridge:默认模式每个容器有独立网络栈,通过虚拟网桥(docker0)和宿主机通信。容器之间用服务名互访(同网络内),外部通过端口映射访问容器。bridge 模式的端口映射(-p 8080:80)有一层 NAT 转换,理论上比 host 慢一点,但隔离性好。自定义网络让容器间可以通过服务名通信:host:直接用宿主机网络容器不隔离网络,直接用宿主机的网络栈。没有 NAT、没有端口映射,性能最好。host 模式的限制:端口冲突——多个容器不能监听同一个端口没有网络隔离——容器能看到宿主机所有网络接口macOS/Windows 上不支持 host 模式(只有 Linux 支持)适合:网络密集型应用(代理、负载均衡)、需要极低延迟的场景。none:无网络容器没有网络接口,只有 loopback。适合纯计算任务(批处理、数据处理),不需要网络的场景。overlay:跨主机网络Docker Swarm 模式下,overlay 网络让不同主机上的容器互相通信:Kubernetes 有自己的网络方案(Flannel、Calico),不用 Docker overlay。怎么选开发/通用场景:bridge + 自定义网络性能优先(Linux):host纯计算/安全隔离:noneSwarm 集群:overlay生产环境:K8s 管网络,不需要选 Docker 网络模式
服务端阅读 06月3日 00:04

Docker 镜像和 Dockerfile 是什么?从零构建镜像详解

Docker 镜像是容器的模板——只读的文件包,包含运行应用所需的一切(代码、运行时、依赖、配置)。Dockerfile 是构建镜像的脚本——一行一个指令,告诉 Docker 怎么组装镜像。镜像是什么镜像是一个分层的文件系统。每个指令(RUN、COPY、ADD)创建一层,层层叠加。好处:多个镜像共享相同的基础层,节省磁盘和拉取时间。镜像本身不可变。运行容器时,Docker 在镜像顶部加一个可写层——容器修改文件只影响这个可写层,不修改镜像。Dockerfile 基本结构逐行解读:FROM:基础镜像,你的镜像在此之上构建WORKDIR:设置工作目录,后续指令都在这个目录下执行COPY:拷贝文件到镜像里RUN:构建时执行命令(安装依赖等)EXPOSE:声明端口(文档作用,不实际映射)CMD:容器启动时执行的命令构建镜像-t myapp:v1 给镜像打标签,. 表示 Dockerfile 在当前目录。Dockerfile 优化技巧1. 利用缓存:Docker 按层缓存。package.json 没变时 npm ci 用缓存,不用重新安装。所以先 COPY package.json 再 COPY 源码——源码变了不影响依赖缓存。2. .dockerignore:排除不需要的文件:不加 .dockerignore 会把 node_modules 也 COPY 进去,既慢又大。3. 多阶段构建:编译和运行分开,运行镜像不需要编译工具。详见多阶段构建专题。镜像标签管理不要只用 latest 标签——无法回滚。用版本号(v1.2.3)或 git commit hash 标记每个镜像。
服务端阅读 06月3日 00:04

Docker 和虚拟机有什么区别?该用哪个?

Docker 和虚拟机都是隔离运行环境的技术,但原理完全不同:虚拟机虚拟整套硬件(含操作系统),Docker 共享宿主机内核只隔离进程。结果:Docker 启动快 10 倍、内存省 5 倍,但隔离性不如虚拟机。核心区别| 维度 | Docker 容器 | 虚拟机 ||------|------------|--------|| 虚拟层级 | 进程级隔离(共享内核) | 硬件级虚拟化(独立内核) || 启动时间 | 秒级 | 分钟级 || 内存占用 | MB 级 | GB 级 || 镜像大小 | MB-Tens of MB | GB 级 || 性能损耗 | 接近原生 | 5-15% || 隔离性 | 弱(共享内核) | 强(独立内核) || 密度 | 单机跑几十个 | 单机跑几个 |为什么 Docker 这么轻虚拟机需要给每个实例装一套完整的操作系统(Linux 内核 + 用户空间),至少 1GB 内存。Docker 容器只是宿主机上的一个进程——用 Linux namespace 隔离进程、网络、文件系统,用 cgroup 限制资源。所有容器共享同一个内核,不需要重复运行操作系统。打个比方:虚拟机是每家自建一栋房子(独立地基、管道),Docker 是同一栋楼里的不同公寓(共享地基和管道,各自有门锁)。Docker 做不到什么因为共享内核,容器不能:运行不同内核版本的系统(Linux 容器不能跑在 Windows 上,反之亦然)完全隔离内核漏洞(一个容器的内核漏洞可能影响宿主机)运行需要特定硬件驱动的应用虚拟机可以——它有独立的内核,可以跑 Windows on Linux、Linux on macOS。什么时候用虚拟机需要运行不同操作系统(Windows + Linux 混合环境)安全要求极高(金融、政务),需要内核级隔离需要直接访问硬件(GPU 直通、特定网卡)合规要求规定必须用虚拟机什么时候用 Docker微服务部署、CI/CD 流水线开发环境统一(所有人跑相同的容器)快速扩缩容90% 的后端应用场景混合使用Docker 跑在虚拟机里是常见架构——云厂商的虚拟机(EC2/ECS)上跑 Docker 容器。虚拟机提供硬件级隔离(多租户安全),Docker 提供应用级隔离(部署灵活性)。
服务端阅读 06月3日 00:02

Docker 健康检查怎么配?HEALTHCHECK 和 Compose 健康检查详解

Docker 健康检查自动检测容器内应用是否正常——容器运行中不代表应用可用。MySQL 可能在做崩溃恢复,Nginx 可能配置错误 502,但容器状态都是 Up。Dockerfile 里配置参数:--interval=30s:每 30 秒检查一次--timeout=5s:5 秒内没响应算失败--retries=3:连续 3 次失败才标记为 unhealthy健康状态:starting(启动中)→ healthy(健康)→ unhealthy(不健康)docker-compose.yml 里配置start_period 很重要——应用启动需要时间(Spring Boot 可能要 30 秒),启动期间的健康检查失败不应该算数。不同应用的健康检查命令depends_on 配合健康检查Compose 的 depends_on 默认只等容器启动,不等应用就绪:condition: servicehealthy 确保 db 真正可用后 web 才启动。比 dependson: db(只等容器启动)更可靠。没有 curl 的镜像Alpine 镜像没有 curl。用 wget 替代:或者安装 curl:RUN apk add --no-cache curl
服务端阅读 06月3日 00:02

Docker 多阶段构建怎么用?减小镜像体积的最佳方法

多阶段构建让 Dockerfile 分多个阶段——编译阶段用完整环境构建产物,运行阶段只拷贝最终产物。结果:镜像从 1GB+ 缩小到 50MB 以下。问题:单阶段构建镜像太大node:20 镜像 1.1GB,加上 node_modules 几百 MB,最终镜像可能 1.5GB。但运行时只需要 dist/ 目录和 node 生产依赖。多阶段构建--from=builder 从第一阶段拷贝指定目录。node:20-slim 只有 200MB,最终镜像约 300MB——比单阶段小 5 倍。Go 应用:极致压缩Go 编译出单个二进制文件,运行时不需要 Go 环境:scratch 是空镜像——里面只有你拷贝的二进制文件。最终镜像可能只有 10-20MB。前端应用:Nginx 托管静态文件前端只需要构建后的 HTML/CSS/JS,不需要 node_modules。nginx:alpine 只有 25MB。COPY --from 的其他用法不限于同一 Dockerfile 的阶段,可以从其他镜像拷贝:从 Caddy 官方镜像里只拷贝二进制文件,不用自己安装。关键要点每个 FROM 开始一个新阶段,只有最后一个阶段的产物进入最终镜像用 AS 命名阶段,COPY --from=名称 引用运行阶段尽量用 slim/alpine 变体不要把源码、编译工具、dev 依赖带进运行镜像
服务端阅读 06月2日 23:47

Docker 容器数据怎么备份和恢复?Volume 备份和数据库导出实战

Docker 容器的数据在 Volume 里——备份 Volume 就是备份数据。两种方式:直接备份 Volume 文件,或从容器内导出。方法一:备份 Volume 目录简单粗暴,但要求停掉容器或确保数据一致性(数据库正在写入时备份可能损坏)。方法二:用临时容器备份不停容器,用 --volumes-from 挂载同一个 Volume:临时容器挂载 pg_data(只读)和宿主机 /backup 目录,把数据打包到宿主机。数据库导出(推荐)数据库不适合直接拷文件——文件可能处于不一致状态。用数据库的导出工具:在容器里执行:导出的是 SQL 文本,保证逻辑一致性,可以跨版本恢复。恢复数据库恢复用 psql/mysql 命令,不用拷文件。自动化备份备份文件要推到远程存储(S3、OSS),不要只存在本机——本机挂了备份也没了。Docker Volume 备份 vs 数据库导出Volume 备份:快,但不保证一致性,适合非数据库文件数据库导出:慢,但保证一致性,适合数据库两者配合:Volume 备份应用配置/上传文件,数据库导出业务数据
服务端阅读 06月2日 23:47

Docker 容器怎么监控?Prometheus + Grafana 和告警配置实战

Docker 监控分两层:容器级(CPU/内存/网络)和应用级(QPS/延迟/错误率)。容器级用 cAdvisor + Prometheus,应用级用代码埋点 + Prometheus。统一在 Grafana 看板和告警。最快上手:docker stats只适合临时查看,没有历史数据、没有告警、没有可视化。Prometheus + cAdvisor:容器级监控cAdvisor 采集容器的 CPU、内存、网络、磁盘 IO 指标,Prometheus 存储和查询,Grafana 可视化。cAdvisor 暴露 /metrics 端点,Prometheus 定时拉取。Grafana 导入 Docker dashboard 模板(ID 893)即可看到容器资源看板。关键监控指标| 指标 | 含义 | 告警阈值 ||------|------|----------|| containercpuusagesecondstotal | CPU 使用率 | > 80% 持续 5 分钟 || containermemoryusagebytes | 内存使用 | > 90% 限制值 || containernetworkreceivebytestotal | 网络接收 | 异常突增 || containeroom_events | OOM 次数 | > 0 立即告警 |OOM 事件是最严重的——容器被杀意味着应用中断,必须立即处理。告警配置Prometheus Alertmanager 配置告警规则和通知渠道:通知渠道支持邮件、Slack、钉钉、企业微信。日志监控监控 + 日志配合:告警触发后用 docker logs 查看对应容器的日志,定位问题。如果用了 Loki,直接在 Grafana 里查日志,不用跳到终端。
服务端阅读 06月2日 23:47

Docker 编排工具怎么选?Docker Compose、Swarm 和 Kubernetes 对比

Docker 编排工具解决的是多容器管理问题——手动 docker run 管几个容器还行,几十个就力不从心了。三个主流方案:Compose(开发)、Swarm(小团队)、Kubernetes(生产)。Docker Compose:开发环境首选一条 docker compose up -d 启动所有服务。适合本地开发、CI 测试、小型项目部署。局限:单机运行,不支持自动扩缩容,没有滚动更新,没有服务发现。服务挂了需要手动重启。Docker Swarm:轻量级集群Swarm 内置在 Docker 里,不需要额外安装。支持多节点集群、滚动更新、服务发现、内置负载均衡。局限:功能比 K8s 少很多——没有自动扩缩容(HPA)、没有自定义调度、没有 CRD 扩展。社区在萎缩,新项目不建议选 Swarm。Kubernetes:生产标准K8s 是容器编排的事实标准。功能完整:自动扩缩容、滚动更新、服务发现、配置管理、密钥管理、持久卷、网络策略、审计日志。K8s 的代价:学习曲线陡、运维复杂、需要专门的平台团队。小项目用 K8s 是杀鸡用牛刀。怎么选1-5 个服务:Docker Compose,简单够用5-20 个服务,单集群:Swarm 或 K8s(建议直接 K8s,Swarm 没有未来)20+ 个服务,多环境:Kubernetes云上部署:直接用云厂商的 K8s 托管服务(EKS/GKE/AKS),别自己搭一句话:开发用 Compose,生产用 K8s。Swarm 跳过。
服务端阅读 06月2日 23:45

Docker 容器权限怎么管?rootless 模式、用户映射和安全加固

Docker 容器默认以 root 运行——如果容器被攻破,攻击者获得容器内的 root 权限,可能逃逸到宿主机。安全的第一步:不要用 root 跑应用。不要用 root 跑应用USER appuser 之后的所有操作(CMD、ENTRYPOINT)都以 appuser 身份执行。即使应用有漏洞,攻击者只有普通用户权限。docker run 指定用户如果 Dockerfile 里没有 USER,运行时指定:覆盖 Dockerfile 里的 USER 指令。UID 1000 通常是宿主机的第一个普通用户。只读文件系统把容器文件系统挂载为只读,攻击者无法写入恶意文件。 给 /tmp 临时写入空间(很多应用需要)。限制能力(Capabilities)Linux capabilities 是细粒度的权限控制。Docker 默认给容器少量 capability,但还可以更严格:删掉所有能力, 只加回绑定 1024 以下端口的能力。按需添加,不给多余的权限。Rootless DockerDocker 默认以 root 运行 daemon——即使容器里不是 root,daemon 本身是 root。Rootless 模式让整个 Docker daemon 以普通用户运行:Rootless 模式的限制:不能绑定 80/443 端口(需要 1024 以上),没有 cgroup 资源限制,网络功能受限。适合 CI/CD 和开发环境,生产环境不成熟。资源限制防止恶意或失控的容器吃光宿主机资源:限制容器最多 100 个进程,防止 fork bomb。安全检查清单容器不以 root 运行(USER 指令或 --user 参数)文件系统只读(--read-only)最小 capability(--cap-drop ALL + 按需 cap-add)资源限制(memory、cpus、pids)不挂载 Docker socket(-v /var/run/docker.sock 是最危险的操作)镜像来自可信源(不用来历不明的镜像)
服务端阅读 06月2日 23:45

Docker 容器怎么更新?滚动更新、蓝绿部署和回滚策略

Docker 容器是不可变的——更新不是在容器里改代码,而是用新镜像替换旧容器。关键是怎么替换才能不停服。最简单的更新:停旧启新有停机时间。适合内部工具、非关键服务。停机时间取决于镜像拉取速度和启动时间。滚动更新:Docker SwarmSwarm 的滚动更新逐个替换容器,始终保持部分实例在线:更新过程中部分容器跑 v1,部分跑 v2,用户无感知。如果新版本有问题,回滚:蓝绿部署准备两套环境(蓝和绿),切换流量:蓝绿部署零停机,但需要双倍资源。适合关键服务的版本更新。Docker Compose 更新Compose 会检测镜像是否变化,只重建有变更的容器。但不是滚动更新——旧容器先停再启新容器,有短暂停机。健康检查确保更新安全更新后容器必须通过健康检查才算成功。连续失败 3 次,标记为 unhealthy。配合滚动更新,unhealthy 的新容器不会继续替换旧容器。回滚策略不管用什么更新方式,都要有回滚能力:保留旧版本镜像(不要 docker rmi 删掉)数据库变更要向后兼容(新版本能读旧 schema)配置文件版本化管理(.env 用 git 追踪)回滚就是 docker run 旧版本镜像。如果数据库迁移了且不兼容旧版本,回滚也救不了——所以数据库变更要分步做。
服务端阅读 06月2日 23:43

Docker 容器日志怎么查看和分析?日志驱动和集中化方案

Docker 日志分两类:容器标准输出(docker logs)和应用自己的日志文件。docker logs应用必须把日志输出到 stdout/stderr 才能用 docker logs 查看。日志轮转(防止磁盘爆满)每个容器最多 3 个文件,每个 10MB,超过自动轮转。集中化日志多容器环境下逐个 docker logs 不现实。用日志聚合:Loki + Grafana:轻量,推荐ELK Stack:功能全但重Fluentd:日志收集器排查技巧grep 过滤:docker logs my-container 2>&1 | grep ERROR多容器:docker compose logs -f启动失败:docker logs $(docker ps -lq)
服务端阅读 06月2日 23:43

Docker 容器网络不通怎么排查?DNS、端口和防火墙问题定位

容器网络问题分三类:容器间不通、容器访问不了外网、外部访问不了容器。按层级排查:DNS → 端口映射 → 防火墙。容器间通信问题不在同一个网络:docker network connect my-network container-b。Docker 内置 DNS 只在同网络内生效。用服务名(ping redis)而不是 IP。ping redis 失败但 IP 能通是 DNS 问题。端口映射问题常见错误:忘了 -p 参数、端口冲突、只绑定了 127.0.0.1。容器访问不了外网DNS 失败时手动指定:docker run --dns 8.8.8.8。防火墙拦截Docker 修改 iptables 实现端口映射。ufw 开启但没放行 Docker 链会拦截流量。确保 DEFAULTFORWARDPOLICY="ACCEPT"。网络模式bridge(默认):需要端口映射和 DNShost:直接用宿主机网络,性能好但隔离差none:无网络生产环境推荐 bridge + 自定义网络。
服务端阅读 06月2日 23:43

Docker 容器内存泄漏怎么排查?OOM 和内存增长定位实战

容器内存持续增长最终被 OOM Kill 是 Docker 最常见的问题之一。排查步骤:确认是不是真的泄漏 → 定位哪个容器 → 找到代码里的泄漏点。确认问题内存持续增长不回落大概率是泄漏。短暂增长后稳定不一定是泄漏——可能是 JVM/Node.js 堆还没触发 GC。检查 OOM 事件容器内定位语言级分析Node.js:用 v8.writeHeapSnapshot() 生成堆快照Java:用 jmap -dump 生成堆 dumpPython:用 tracemalloc 或 objgraph用 Chrome DevTools 或 MAT 分析堆快照,找 Retained Size 最大的对象。常见原因全局列表/缓存只加不删(最常见)事件监听器没有 removeEventListener数据库连接没有释放临时缓解只治标不治本,排查期间防止 OOM Kill。
服务端阅读 06月2日 23:43

Docker 怎么配合微服务架构?一个服务一个容器的实践方法

微服务架构的核心是一个服务一个进程——Docker 的容器天然就是为此设计的。每个微服务打包成独立镜像,独立部署、独立扩缩容。一个服务一个容器每个服务有自己的 Dockerfile,独立构建和部署。服务间通信同一 Docker 网络内,容器用服务名互相访问(http://user-service:3000),Docker 内置 DNS 自动解析。跨主机通信需要额外方案:Docker Swarm 用 Overlay 网络,Kubernetes 用 Service。扩缩容Compose 的 --scale 适合开发测试。生产环境用 K8s 的 HPA 自动扩缩容。配置管理不要把配置写死在镜像里。用环境变量注入,不同环境用不同 .env 文件。Docker 微服务的局限Docker Compose 适合 5-10 个服务的项目。超过 10 个服务应该用 Kubernetes——它提供自动扩缩容、滚动更新、服务发现、健康检查等编排能力。
服务端阅读 06月2日 23:40

Docker 跑数据库怎么做?数据持久化、备份和生产环境注意事项

Docker 跑数据库能快速搭建开发环境,但生产环境要格外注意数据持久化、性能和备份。一句话:开发环境放心用,生产环境谨慎用。数据持久化:第一优先级容器删了数据就没了——这是 Docker 跑数据库最大的风险。必须用 Volume 挂载数据目录:# MySQL:把 /var/lib/mysql 挂到宿主机docker run -d -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret mysql:8# PostgreSQL:把 /var/lib/postgresql/data 挂出来docker run -d -v pg_data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:16-v mysql_data:/var/lib/mysql 用 Docker Volume(推荐)。也可以用宿主机目录 -v /data/mysql:/var/lib/mysql,但权限管理更麻烦。验证持久化:docker rm -f 删掉容器后重新 docker run(同一个 volume),数据还在。开发环境:docker-compose 一键搭建# docker-compose.ymlservices: db: image: postgres:16 environment: POSTGRES_DB: myapp POSTGRES_PASSWORD: dev123 ports: - "5432:5432" volumes: - pg_data:/var/lib/postgresql/data redis: image: redis:7 ports: - "6379:6379"volumes: pg_data:docker compose up -d 一键启动 PostgreSQL + Redis。ports 映射到宿主机,本地开发工具(psql、DBeaver)直连。生产环境的注意事项1. 不要把数据库密码放环境变量环境变量对容器内所有进程可见,docker inspect 也能看到。用 Docker Secrets(Swarm)或文件挂载:docker run -d -v /secrets/db_password.txt:/run/secrets/db_password:ro mysql:82. 设置资源限制数据库不加限制会吃光宿主机内存:docker run -d --memory=4g --memory-swap=4g --cpus=2 mysql:8同时在数据库配置里调整缓存大小(MySQL 的 innodb_buffer_pool_size,PG 的 shared_buffers),让它和容器内存限制匹配。3. 健康检查healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 3容器"运行中"不代表数据库"可用"——MySQL 可能还在做崩溃恢复。健康检查确保只有真正可用的数据库接收连接。备份# MySQL 备份docker exec mysql_container mysqldump -u root -p secret mydb > backup.sql# PostgreSQL 备份docker exec pg_container pg_dump -U postgres mydb > backup.sql定时备份:用 cron + 上面的命令,把 SQL 文件推到 S3 或其他存储。不要把备份文件存在容器里。生产环境该不该用 Docker 跑数据库?适合:中小规模、团队能力有限、需要快速部署。用 Docker 统一运维比手动管理多台数据库服务器简单。不适合:高性能场景(I/O 密集型数据库)、需要极致调优、合规要求禁止容器化。Docker 的网络和存储有一层抽象,理论上比裸机慢 5-10%。大多数场景这 5% 可以忽略,但对 TPS 要求极高的系统可能不行。折中方案:数据库跑在宿主机或托管数据库(RDS),只把应用放在 Docker 里。
服务端阅读 06月2日 23:16

Next.js App Router 怎么定义路由?动态路由、布局嵌套和 loading 详解

Next.js App Router 用文件系统定义路由——文件夹结构就是 URL 结构。每个路由三要素:page.tsx(页面)、layout.tsx(布局)、loading.tsx(加载状态)。基本路由映射app/├── page.tsx → /├── about/│ └── page.tsx → /about├── blog/│ ├── page.tsx → /blog│ └── [slug]/│ └── page.tsx → /blog/:slug(动态路由)├── dashboard/│ ├── layout.tsx → dashboard 共享布局│ ├── page.tsx → /dashboard│ └── settings/│ └── page.tsx → /dashboard/settingspage.tsx 是必须的——没有 page.tsx 的文件夹不构成路由。其他文件(layout、loading、error)都是可选的。动态路由// app/blog/[slug]/page.tsxexport default function BlogPost({ params }: { params: { slug: string } }) { return <h1>文章: {params.slug}</h1>;}访问 /blog/hello-world 时,params.slug = "hello-world"。Catch-all 路由用 [...slug]:匹配 /shop/clothes/shirts 这样的多级路径,params.slug = ["clothes", "shirts"]。路由组用 (groupName) 创建不反映在 URL 中的分组:app/├── (marketing)/│ ├── about/page.tsx → /about(URL 里没有 marketing)│ └── pricing/page.tsx → /pricing├── (shop)/│ ├── products/page.tsx → /products│ └── cart/page.tsx → /cart路由组的用途:给一组页面共享 layout 而不影响 URL。比如 (marketing) 组用营销页布局,(shop) 组用商店布局。布局嵌套layout.tsx 会嵌套——子路由的 layout 包在父 layout 里面:// app/layout.tsx — 根布局(必有,包含 html/body)export default function RootLayout({ children }) { return ( <html> <body> <nav>全局导航</nav> {children} </body> </html> );}// app/dashboard/layout.tsx — dashboard 布局export default function DashboardLayout({ children }) { return ( <div className="flex"> <aside>侧边栏</aside> <main>{children}</main> </div> );}从 /dashboard 切换到 /dashboard/settings 时,根布局和 dashboard 布局都不会重新渲染。只有 children 对应的 page.tsx 更新。loading.tsx:自动 Suspense// app/dashboard/loading.tsxexport default function Loading() { return <div className="animate-pulse">加载中...</div>;}Next.js 自动用 Suspense 包裹 page.tsx,加载时显示 loading.tsx。不需要手写 useState 管理加载状态。error.tsx:错误边界// app/error.tsx — 必须是 Client Component'use client'export default function Error({ error, reset }) { return ( <div> <p>出错了: {error.message}</p> <button onClick={reset}>重试</button> </div> );}error.tsx 捕获子组件的运行时错误,显示错误界面。reset 函数重新渲染出错的组件。程序化导航'use client'import { useRouter } from 'next/navigation';function LoginButton() { const router = useRouter(); return <button onClick={() => router.push('/dashboard')}>登录</button>;}router.push() 客户端跳转,router.replace() 替换当前历史记录(不能回退),router.back() 返回上一页。Server Component 里用 redirect():import { redirect } from 'next/navigation';export default async function Page() { const session = await getSession(); if (!session) redirect('/login'); // ...}redirect 在服务端执行,用户看不到中间页面。
服务端阅读 06月2日 23:15

Next.js SSR、SSG 和 ISR 有什么区别?怎么选?

SSR、SSG、ISR 是三种不同的页面渲染策略,区别在于 HTML 什么时候生成。选哪个取决于数据的更新频率和页面的实时性要求。三种策略对比| 策略 | HTML 生成时机 | 适合场景 | 性能 ||------|-------------|----------|------|| SSG | 构建时 | 博客、文档、营销页 | 最快(CDN 缓存) || SSR | 每次请求时 | 仪表盘、个人主页 | 中等(服务端计算) || ISR | 构建时 + 定时更新 | 商品列表、新闻 | 接近 SSG 的速度 |SSG(Static Site Generation)构建时生成 HTML,部署后不变化。速度最快——CDN 直接返回静态文件,零服务端计算。// App Router 默认就是 SSG// 没有 dynamic 数据获取的页面自动静态生成export default function AboutPage() { return <h1>关于我们</h1>;}// 带数据的 SSG:构建时获取async function BlogList() { const posts = await db.post.findMany(); // 构建时执行 return posts.map(p => <article key={p.id}>{p.title}</article>);}局限:数据变化后需要重新构建部署。适合不常变的内容。SSR(Server-Side Rendering)每次请求时生成 HTML。数据始终最新,但每次请求都有服务端计算开销。// App Router: 使用动态数据获取自动触发 SSRasync function Dashboard() { const stats = await fetch('https://api.example.com/stats', { cache: 'no-store' // 不缓存,每次请求重新获取 }).then(r => r.json()); return <div>{stats.users} 用户</div>;}cache: 'no-store' 告诉 Next.js 这个请求不能缓存,必须每次执行。适合实时数据。ISR(Incremental Static Regeneration)SSG 的升级版——静态生成 HTML,但后台定时重新生成。兼具 SSG 的速度和数据的新鲜度。async function ProductList() { const products = await fetch('https://api.example.com/products', { next: { revalidate: 3600 } // 每 3600 秒(1 小时)重新验证 }).then(r => r.json()); return products.map(p => <div key={p.id}>{p.name}</div>);}ISR 的工作流程:第一个用户请求 → 返回缓存的静态 HTML(快)后台检查 revalidate 时间是否到期到期后重新生成 HTML,替换旧缓存下一个用户请求 → 返回新生成的 HTML关键点:用户永远看到的是缓存的页面(快),后台异步更新。最坏情况数据延迟 revalidate 秒。怎么选选 SSG:内容几乎不变(文档、博客、营销页)选 ISR:内容定期更新(商品列表、新闻、排行榜),能接受短暂延迟选 SSR:内容必须实时(仪表盘、用户个人页、搜索结果)常见错误:所有页面都用 SSR。大部分页面用 ISR 就够了——1-5 分钟的数据延迟用户感知不到,但性能提升显著。App Router 中的 revalidate 策略// 定时重新验证(ISR)fetch(url, { next: { revalidate: 60 } }); // 60 秒// 按需重新验证:修改数据后手动触发import { revalidateTag, revalidatePath } from 'next/cache';// 在 Server Action 里触发async function updateProduct(formData: FormData) { await db.product.update({ ... }); revalidateTag('products'); // 刷新所有 products 标记的缓存 revalidatePath('/products'); // 刷新 /products 页面}按需重新验证(On-Demand Revalidation)比定时更精准——数据变了立即刷新,没变就不浪费资源。
服务端阅读 06月2日 23:14

Next.js Pages Router 和 App Router 有什么区别?该不该迁移?

Pages Router 是 Next.js 的原始路由系统,App Router 是 13+ 引入的新系统。核心区别:App Router 基于 React Server Components,默认在服务端渲染;Pages Router 默认在客户端渲染。新项目用 App Router,老项目不急迁移。架构对比| 维度 | Pages Router | App Router ||------|-------------|------------|| 目录 | pages/ | app/ || 默认渲染 | 客户端 | 服务端(RSC) || 数据获取 | getServerSideProps / getStaticProps | async 组件 + fetch || 布局 | _app.tsx + 全局 Layout | 嵌套 layout.tsx || 路由 | 文件即路由 | 文件夹 + page.tsx || API | pages/api/ | app/ + route.ts || Loading | 手动管理 | loading.tsx 自动 |数据获取的变化Pages Router 用特殊函数获取数据:// Pages Routerexport async function getServerSideProps() { const data = await fetchData(); return { props: { data } };}export default function Page({ data }) { return <div>{data}</div>;}App Router 直接在组件里 async/await:// App Routerexport default async function Page() { const data = await fetchData(); // 服务端直接执行 return <div>{data}</div>;}App Router 的方式更直观——不需要记特殊函数名,数据获取就是普通的函数调用。布局系统Pages Router 的布局是全局的(_app.tsx),切换页面时整个布局重新渲染。App Router 支持嵌套布局——每个目录可以有 layout.tsx,子路由切换时父布局不重新渲染:app/├── layout.tsx # 根布局(导航栏、页脚)— 永远不重新渲染├── dashboard/│ ├── layout.tsx # 仪表盘布局(侧边栏)— 路由切换不重新渲染│ ├── page.tsx # /dashboard│ └── settings/│ └── page.tsx # /dashboard/settings用户从 /dashboard 切换到 /dashboard/settings 时,根布局和仪表盘布局都保持不变,只有 settings 的 page.tsx 重新渲染。这在 Pages Router 里做不到。何时迁移不急迁移的情况:项目稳定运行,没有性能问题团队不熟悉 RSC,迁移风险高大量自定义 _document.tsx / _app.tsx 逻辑应该迁移的情况:需要更好的首屏性能(RSC 显著减少 JS 体积)嵌套布局能解决当前的布局闪烁问题项目刚开始或处于早期迁移可以渐进式——两个路由可以共存。先把新页面用 App Router 写,老页面逐步迁移。两者可以共存pages/├── index.tsx # Pages Router 处理├── about.tsx # Pages Router 处理app/├── dashboard/│ └── page.tsx # App Router 处理Next.js 13+ 同时支持两套路由,同一个项目里渐进迁移。但要注意:同一路径不能两边都定义(pages/dashboard.tsx 和 app/dashboard/page.tsx 会冲突)。
服务端阅读 06月2日 23:13

Next.js 认证怎么做?NextAuth.js 配置 OAuth 和凭证登录实战

Next.js 认证最主流的方案是 NextAuth.js(v5 改名 Auth.js)。它处理了 OAuth、JWT、Session 管理等所有细节,30 分钟就能搭好 Google/GitHub 登录。最快上手:NextAuth.jsnpm install next-auth@beta// app/api/auth/[...nextauth]/route.tsimport NextAuth from 'next-auth';import GitHub from 'next-auth/providers/github';export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }), ], pages: { signIn: '/login', // 自定义登录页 },});export const { GET, POST } = handlers;// middleware.ts — 保护路由export { auth as middleware } from './app/api/auth/[...nextauth]/route';export const config = { matcher: ['/dashboard/:path*'],};三步:配置 provider → 创建 API 路由 → 加中间件保护。用户访问 /dashboard 时如果没有登录,自动跳转到 /login。在 Server Component 里获取 Sessionimport { auth } from './app/api/auth/[...nextauth]/route';export default async function Dashboard() { const session = await auth(); if (!session) { redirect('/login'); } return <h1>欢迎, {session.user.name}</h1>;}auth() 在服务端获取 session,不需要客户端 JavaScript。这是 RSC 的优势——认证逻辑完全在服务端,客户端零开销。在 Client Component 里获取 Session'use client'import { useSession } from 'next-auth/react';export default function Profile() { const { data: session, status } = useSession(); if (status === 'loading') return <p>加载中...</p>; if (!session) return <p>请先登录</p>; return <p>{session.user.name}</p>;}useSession 通过轮询 /api/auth/session 获取 session。更高效的方式是用 SessionProvider:// app/providers.tsx'use client'import { SessionProvider } from 'next-auth/react';export function Providers({ children }) { return <SessionProvider>{children}</SessionProvider>;}包在 layout 里后,useSession 不再轮询,而是通过 Context 共享 session 数据。自定义登录页默认登录页太简陋。自定义页面:// app/login/page.tsximport { signIn } from '@/auth';export default function LoginPage() { return ( <form action={async () => { 'use server' await signIn('github'); }}> <button type="submit">用 GitHub 登录</button> </form> );}用 Server Action 触发 signIn,比客户端 signIn() 更简洁。凭证登录(用户名密码)OAuth 不够时,加 Credentials provider:import Credentials from 'next-auth/providers/credentials';import bcrypt from 'bcryptjs';export const { handlers, auth } = NextAuth({ providers: [ Credentials({ credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { const user = await db.user.findUnique({ where: { email: credentials.email }, }); if (!user) return null; const valid = await bcrypt.compare(credentials.password, user.passwordHash); if (!valid) return null; return { id: user.id, name: user.name, email: user.email }; }, }), ],});authorize 返回 null 表示认证失败,返回 user 对象表示成功。密码必须用 bcrypt 哈希,不要存明文。常见问题Session 过期后页面不刷新:用 SessionProvider 的 refetchInterval 定时刷新:<SessionProvider refetchInterval={300}> 每 5 分钟检查一次。OAuth 回调 404:确保回调 URL 在 OAuth provider 里配置正确。GitHub 在 Settings > Developer > OAuth Apps 里配。部署后 Cookie 不工作:NEXTAUTH_URL 环境变量必须设为生产域名。Vercel 部署时自动设置,其他平台需要手动配。