一、引言:静态页面的“重复代码”之痛

在纯静态网站或前后端未完全分离的项目中,我们常常会遇到这样的困境:

  • 头部(Header)、尾部(Footer)、导航栏(Navigation)等公共组件在每个 HTML 页面中都要重复编写。
  • 修改一个公共组件,需要手动同步到几十甚至上百个页面,极易出错和遗漏。

传统的解决方案是使用前端框架(如 React/Vue)进行组件化。但对于简单的营销页、文档站或遗留系统,引入重型框架显得“杀鸡用牛刀”。

有没有一种轻量级方案,能让静态 HTML 也拥有“包含”(include)

答案就是:SSI (Server Side Include) —— 一种由 Web 服务器在响应前动态拼接文件的技术。

💡 核心价值
无需任何前端框架或后端语言,仅通过 Nginx 配置,即可实现静态 HTML 文件的模块化、复用和动态组装,大幅提升开发效率和可维护性


二、SSI 是什么?工作原理揭秘

1. 基本概念

SSI (Server Side Include),即服务器端包含,是一种由 Web 服务器(如 Apache, Nginx)提供的功能。它允许在 HTML 文件中嵌入特殊的指令,服务器在将页面发送给客户端之前,会先执行这些指令,并将结果替换到原位置。

2. 核心指令:<!--#include file="..." -->

这是最常用的 SSI 指令,用于包含另一个文件的内容。

3. 工作流程

  1. 客户端请求 index.html
  2. Nginx 发现 index.html 中包含 SSI 指令。
  3. Nginx 读取被包含的文件(如 header.htmlfooter.html)。
  4. Nginx 将所有内容拼接成一个完整的 HTML 文档。
  5. 将最终的完整文档返回给客户端。

对浏览器而言,它收到的就是一个普通的、完整的 HTML 文件,完全感知不到背后的“拼装”过程


三、Nginx SSI 模块配置实战

Step 1: 确认模块已安装

Nginx 的 SSI 功能由 ngx_http_ssi_module 模块提供。该模块默认已编译进官方 Nginx 包,通常无需额外安装。

可通过以下命令验证:

nginx -V 2>&1 | grep -o with-http_ssi_module
# 如果有输出,则表示模块已存在

Step 2: 开启 SSI 支持

在你的 server 或 location 块中添加配置:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # 方式一:为特定类型文件开启 SSI
    location ~* \.shtml$ {
        ssi on;                     # 开启 SSI
        ssi_silent_errors on;       # 静默处理错误,不显示错误信息
        ssi_types text/html;        # 指定处理的 MIME 类型
    }

    # 方式二:为整个站点开启(谨慎使用)
    # location / {
    #     ssi on;
    # }
}
关键指令详解
指令 默认值 说明
ssi on/off off 核心开关,启用/禁用 SSI 处理
ssi_silent_errors on/off off 开启后,SSI 指令执行错误时不会在页面上显示 [an error occurred while processing the directive]
ssi_types text/html 指定哪些 MIME 类型的响应体需要被 SSI 过滤器处理

✅ 最佳实践不要直接对 .html 文件开启 SSI!这会导致 Nginx 对每一个 HTML 请求都进行扫描,带来不必要的性能开销。推荐使用独立的后缀名,如 .shtml

Step 3: 编写 SSI 指令

创建你的页面文件 /var/www/html/index.shtml

<!DOCTYPE html>
<html>
<head>
    <title>我的主页</title>
</head>
<body>
    <!--#include virtual="/common/header.shtml" -->
    
    <main>
        <h1>欢迎来到我的网站!</h1>
        <p>这里是首页的独有内容。</p>
    </main>

    <!--#include virtual="/common/footer.shtml" -->
</body>
</html>

创建公共头部文件 /var/www/html/common/header.shtml

<header>
    <nav>
        <a href="/">首页</a>
        <a href="/about">关于</a>
        <a href="/contact">联系</a>
    </nav>
</header>

创建公共尾部文件 /var/www/html/common/footer.shtml

<footer>
    <p>&copy; 2026 我的公司. 保留所有权利。</p>
</footer>

四、高级用法与技巧

1. file vs virtual 参数

  • file: 指定相对于当前文件所在目录的路径。

    <!-- 当前文件在 /pages/,此指令会查找 /pages/includes/nav.html -->
    <!--#include file="includes/nav.html" -->
  • virtual: 指定相对于网站根目录root)的 URI 路径。

    <!-- 无论当前文件在哪,此指令都会查找 /common/nav.html -->
    <!--#include virtual="/common/nav.html" -->

✅ 推荐使用 virtual,因为它更灵活,不受文件物理位置影响。

2. 条件判断与变量(有限支持)

SSI 还支持一些简单的编程逻辑,虽然远不如后端语言强大,但在特定场景下非常有用。

<!-- 设置变量 -->
<!--#set var="site_name" value="My Awesome Site" -->

<!-- 使用变量 -->
<title><!--#echo var="site_name" --></title>

<!-- 条件判断 -->
<!--#if expr="$HTTP_USER_AGENT = /Mobile/" -->
    <link rel="stylesheet" href="mobile.css">
<!--#else -->
    <link rel="stylesheet" href="desktop.css">
<!--#endif -->

3. 与缓存策略结合

SSI 是在每次请求时动态执行的,这意味着即使原始文件未变,响应也可能不同(例如,基于 User-Agent 的条件判断)。因此,对包含 SSI 指令的页面应谨慎设置缓存

location ~* \.shtml$ {
    ssi on;
    # 不设置长期缓存,或根据业务逻辑设置较短的缓存时间
    expires 10m;
}

五、常见问题与避坑指南

1. Q: 为什么我的 SSI 指令没有生效

A: 最常见的原因有三个:

  • 文件后缀名不对:确保你开启了对应后缀名(如 .shtml)的 SSI。
  • 指令语法错误:检查注释格式是否为 <!--#directive ... -->,注意 # 后面的空格。
  • 被包含文件路径错误:使用 virtual 时,路径是相对于 root 的 URI,不是服务器上的绝对路径。

2. Q: SSI 会影响性能吗

A: 会,但影响可控。Nginx 需要读取多个文件并进行字符串拼接。对于高并发、低复杂度的静态站,影响微乎其微。但如果一个页面包含了数十个文件,或者被包含的文件本身很大,则会产生可观的 I/O 开销。建议只用于组装少量、小型的公共组件

3. Q: SSI 和 Nginx concat 模块有什么区别

A: 两者目标完全不同。

  • SSI:面向开发者,解决的是代码复用和维护问题。它在服务端组装的是结构化的 HTML 片段
  • Concat:面向网络传输,解决的是HTTP/1.1 并发限制问题。它在服务端合并的是同类型的静态资源(如多个 JS 文件)。

4. Q: 现代前端框架下还有必要用 SSI 吗

A!在以下场景依然非常有价值:

  • 营销落地页(Landing Page):需要快速上线、SEO 友好、无复杂交互。
  • 文档网站:如使用 Sphinx、MkDocs 生成的静态文档。
  • 混合架构:部分页面由后端渲染(SSR),部分是纯静态,可以用 SSI 统一公共布局。

六、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐