返回笔记列表
01 · NOTES / WebGIS 开发

第 27 节 · 与地图集成模式(模块 03 收官)

2026 年 9 月 1 日

第 27 节 · 与地图集成模式(模块 03 收官)

📌 版本信息:Next.js 15.x / Leaflet 1.9.x / MapLibre 4.x(2026-08-29 核对) 📚 来源:next/dynamic | 各地图库官方文档 🎯 定位:模块 03 最后一节——把地图(CSR 世界的宠儿)安全地装进 Next(SSR 世界),也是 C-03b/C-03c 的技术核心。

一、这一节的目标

  1. 理解"地图库不能 SSR"的根因(window 依赖)
  2. 精通两种动态导入方案:next/dynamic(ssr: false)与客户端岛内懒加载
  3. 掌握"服务端取数 → 客户端地图"的标准数据流
  4. 掌握地图站 SEO 的正确姿势
  5. 领取模块 03 结业清单

二、为什么地图库必须关 SSR

Leaflet/MapLibre/Cesium 初始化时就要访问 window/document(量容器尺寸、建 WebGL 上下文)。服务端没有这些——直接 import 会在服务器渲染时报 window is not defined。而且地图是纯交互产物,SSR 它也没有收益。

解法:客户端岛 + 动态导入(ssr: false),跳过服务端渲染环节:

// app/explore/page.js —— 服务端壳:取数 + 元数据(SEO 部分)
import dynamic from 'next/dynamic';

// ssr:false:这个组件只在浏览器加载渲染(地图库就不会碰 window)
const MapClient = dynamic(() => import('./MapClient'), {
  ssr: false,
  loading: () => <div style={{ height: 400, background: '#e8eef4' }}>地图加载中…</div>,
});

export const metadata = { title: '城市探索' };

export default async function ExplorePage() {
  const pois = await getPois();               // 服务端取数(密钥安全、零白屏)
  return (
    <>
      <h1>城市探索</h1>
      <MapClient pois={pois} />                {/* 可序列化 props 传给客户端岛 */}
    </>
  );
}
// MapClient.js —— 客户端岛:只有这个文件(+地图库)进浏览器包
'use client';
import { useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

export default function MapClient({ pois }) {
  const ref = useRef(null);
  useEffect(() => {
    const map = L.map(ref.current).setView([30.59, 114.31], 10);
    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
    pois.forEach((p) => L.marker([p.lat, p.lng]).addTo(map).bindPopup(p.name));
    return () => map.remove();               // 清理纪律(模块 02 精神)
  }, [pois]);
  return <div ref={ref} style={{ height: 400 }} />;
}

备选写法:不用 dynamic 包,而是在 "use client" 岛的 useEffectawait import('leaflet') 再初始化——两种都行,dynamic 写法更声明式(推荐)。MapLibre/Cesium 同款模式(Cesium 在模块 16 的 React 集成节细讲)。


三、地图站 SEO 的正确姿势

地图本身对 SEO 是黑盒,SEO 靠的是"地图之外的 HTML"

  1. 页面文案服务端渲染:城市介绍、数据摘要写在服务端组件里(爬虫可见)
  2. 每城一个动态路由页/citys/[slug])+ generateMetadata(标题/描述/OG 图)——第 21 节
  3. 给地图配"表格替代视图":数据列表本身就是可索引内容(也是无障碍要求,模块 02 第 28 节的一鱼两吃)
  4. sitemap.xml:Next 内置 app/sitemap.js 导出全站 URL 列表

💡 记住分工:地图给用户玩,HTML 给爬虫读——两者都要,各干各的。


四、自测题

  1. 地图库 SSR 报错的根因?两种解法的取舍?
  2. "服务端取数 + 客户端地图"的数据流三步?
  3. 地图站的 SEO 从哪来?
  4. dynamic 的 loading 配置解决什么体验问题?
  5. 模块 03 结业自检——全部答出即通关: ① SSR/SSG/ISR/CSR 各举一例 ② "use client" 三信号 ③ revalidateTag 模式两步 ④ 服务端组件三大限制 ⑤ Route Handler 与 FastAPI 的分工

参考答案

  1. 初始化依赖 window/document/WebGL 上下文;解法:dynamic(ssr:false) 或 useEffect 内动态 import——前者声明式推荐,后者控制粒度更细。
  2. 服务端取数(密钥安全)→ 可序列化 props 传岛 → 岛内 useEffect 初始化地图并清理。
  3. 服务端渲染的文案、动态路由页 + metadata、表格替代视图、sitemap。
  4. ssr:false 的岛在 chunk 下载完成前显示占位,避免布局塌陷与白块。
  5. 见第 19/23/26 节答案索引——全答对说明模块 03 的 Next 部分真正内化。

🎉 模块 03 · React 与 Next.js 全部 27 节完结

你已拥有:React 全栈(组件/state/Effect/Router/Context/Hooks 原理/性能/TS 模式)+ Vue 读码能力 + Next 全栈(App Router/渲染策略/缓存/Route Handler/地图集成)。

收尾动作:

  1. 完成综合项目 C-03a 城市信息小站C-03b 地图导览站C-03c 个人博客(压轴,任务书在各自 practice/ 文件夹)
  2. 学习计划.md 打卡
  3. 进入 模块 04 · GIS 基础(9 节 + C-04 坐标转换工具站)——从这里开始,Web 地图的世界正式开门。
TAGSweb