Thymeleaf在 标签上使用th:fragment,会移除 ,从而使浏览器进入怪异模式

前端开发 2026-07-09

我在使用Spring Boot搭配Thymeleaf,并且有一个所有页面共用的布局模板。布局定义如下:

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      th:fragment="layout (content, pageTitle)">
<head>...</head>
<body>
    <th:block th:replace="${content}"></th:block>
</body>
</html>

我的页面引用它的方式如下:

<html th:replace="~{fragments/layout :: layout(content = ~{::content}, pageTitle = 'MY PAGE')}">
<body>
    <th:block th:fragment="content">
        <div class="container-fluid">...</div>
    </th:block>
</body>
</html>

在浏览器查看页面源代码时,<!DOCTYPE html> 声明在渲染输出中缺失。页面直接以 <html> 开头。这会导致浏览器进入怪异模式。

为什么DOCTYPE会被移除,以及如何修复?

解决方案

为何会这样

这是Thymeleaf处理片段时的已知限制。当Thymeleaf渲染 th:fragment 时,它会将其视为一个文档节点,而不是一个完整的文档。<!DOCTYPE html> 声明并不是一个元素,它位于元素树之外,因此在提取并渲染片段时,DOCTYPE会被悄无声息地丢失。

<html> 标签上放置 th:fragment 的做法在教程和Stack Overflow的答案中被广泛展示,但没有人提到这个后果。使用这种模式的人在浏览器中会悄悄进入怪异模式。

怪异模式是一种浏览器兼容性模式,最初用于在现代HTML/CSS标准出现之前构建的旧网站。没有DOCTYPE时,浏览器会假设页面很旧,并改变某些CSS属性的表现,尤其是box-sizing(盒子模型尺寸计算方式)和布局。

在实际应用中,大多数页面仍然能正确渲染,因为现代浏览器较为宽容,但这在技术上是错误的,可能引发微妙且难以诊断的布局错误,尤其是在使用像Bootstrap这样的框架且它假设处于标准模式时。

变通办法——无需额外依赖

不要让页面模板通过 th:replace 调用布局,而是颠倒关系。让控制器直接返回布局,并将内容页作为模型属性传入:

@GetMapping("/mypage")
public String region(Model model) {
    model.addAttribute("contentPage", "pages/mypage");
    model.addAttribute("pageTitle", "MY PAGE");
    return "fragments/layout";
}

layout.html 中,将硬编码的 ${content} 注入替换为:

<th:block th:replace="~{${contentPage} :: content}"></th:block>

并简化你的页面模板——不再在 <html> 上使用 th:replace

<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="content">
    <div class="container-fluid">...</div>
</th:block>
</body>
</html>

由于 layout.html 现在是正在渲染的实际模板,而不是一个片段,Thymeleaf将其输出为完整文档,DOCTYPE将被保留。

替代方案——Thymeleaf Layout Dialect

值得注意的是,Thymeleaf内置的片段系统并非为完整的页面布局设计。片段应是可复用的HTML局部块,而不是布局包装器。将 th:fragment 放在 <html> 标签上几乎能工作,但会悄悄丢失DOCTYPE,这是把它们用于并非为此设计的用途所带来的副作用。

社区意识到这个空缺, ultraq 创建了 Thymeleaf Layout Dialect,这是为此用例专门构建的一个小型依赖。它能够正确处理DOCTYPE,并提供更简洁的语法:

添加依赖项(Maven):

<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

注:截至本文撰写时,Thymeleaf Layout Dialect的依赖项存在已知漏洞。在将其用于生产项目之前,请先核对当前状态。

你的页面模板将变成:

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{fragments/layout}">
<body>
    <div layout:fragment="content">
        <div class="container-fluid">...</div>
    </div>
</body>
</html>

并在 layout.html 中标记内容区域:

<div layout:fragment="content"></div>

控制器将恢复正常返回页面模板,DOCTYPE将被正确保留。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章