最可靠方式是 fetch + innerhtml 动态加载同源 html 片段,需确保 http(s) 环境、服务端正常响应且无 cors 问题;构建期推荐 webpack/vite 插件合并,大型项目应采用 ssr/ssg 方案。

用 fetch + innerHTML 动态加载 HTML 片段最可靠
现代浏览器不支持原生的 include 语法(如 PHP 的 include 或 JSP 的 jsp:include),纯静态 index.html 无法直接引入外部 HTML 文件。最通用、可控的方式是用 JavaScript 发起 fetch 请求,把片段内容取回来再插入 DOM。
注意:这要求服务端能正常返回 HTML(不能是 404 或 CORS 拒绝),且页面必须在 HTTP(S) 协议下运行——直接双击打开 file:// 会触发跨域限制。
示例:
fetch('header.html')
.then(res => res.text())
.then(html => document.getElementById('header-container').innerHTML = html);
-
header.html必须是同源路径(如和index.html同目录或子目录) - 插入前建议清空容器,避免重复渲染;也可用
replaceWith()替换整个节点 - 如果片段含
<script></script>标签,innerHTML不会自动执行,需手动eval或创建script元素(不推荐)
Webpack/Vite 等构建工具里用 html-loader 或 @vitejs/plugin-html
如果你项目已用 Webpack 或 Vite,就不该在运行时拼 HTML,而应在构建期处理。这类方案能提前合并模板、支持变量、压缩、类型检查,也避免了首屏闪动。
Webpack 示例(webpack.config.js):
module.exports = {
module: {
rules: [{
test: /\.html$/i,
loader: 'html-loader',
options: { sources: false }
}]
}
};然后在 index.html 中写:<div id="nav"></div>
-
html-loader默认会把require('./xxx.html')当作字符串导入,适合做模板拼接 - Vite 用户可用
@vitejs/plugin-html+inject配置,或更轻量的vite-plugin-html - 注意:这些只在开发/构建阶段生效,最终生成的
index.html是静态合并后的结果,不依赖 JS 运行
服务端渲染(SSR)或静态站点生成器(SSG)才是正解场景
如果你频繁需要复用 HTML 片段(比如页头、页脚、侧边栏),说明项目已超出纯静态页面范畴。硬用前端 JS 加载只是权宜之计,容易出错、难调试、SEO 友好性差。
更合理的路径是:
- 小项目 → 用
Eleventy(.11ty.js)或Hugo,天然支持{% include "footer.html" %} - 中大型项目 → 用
Next.js(App Router)、Nuxt或SvelteKit,组件级复用比 HTML 片段更安全 - 已有 Node 服务 → 直接用
fs.readFileSync('./footer.html', 'utf8')在响应中拼接
强行在客户端用 JS 拼 HTML,等于把本该由服务端/构建工具干的事拖到浏览器里,还绕不开 CSP、CORS、加载时白屏等问题。
别碰 <iframe></iframe> 和 document.write
有人会想到用 <iframe src="footer.html"></iframe>,但 iframe 是独立上下文:样式隔离、事件不冒泡、高度自适应困难、SEO 基本不可见。而 document.write 在页面加载完成后调用会清空整个文档,早已被现代开发弃用。
这两个方案看似“简单”,实则埋坑极深:
-
<iframe></iframe>无法共享父页面的 CSS 变量或 JS 状态,登录态、主题切换都得额外通信 -
document.write在 Chrome 95+ 已对延迟调用抛出警告,未来可能彻底禁用 - 二者都不支持模块化、无法参与构建流程、调试成本高
真正省事的方式,是选对阶段:构建期合并就用构建工具,运行时动态就用 fetch + 显式错误处理,别为了“少写一行”去踩已知的兼容性雷。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











