Skip to content

Commit 3c2f4d2

Browse files
authored
fix: improve hydration of svelte head blocks (#11099)
* fix: improve hydration of svelte head blocks * revert sandbox * simplify
1 parent 48549f7 commit 3c2f4d2

File tree

8 files changed

+63
-10
lines changed

8 files changed

+63
-10
lines changed

.changeset/proud-queens-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: improve hydration of svelte head blocks

packages/svelte/src/internal/client/dom/blocks/svelte-head.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js';
22
import { empty } from '../operations.js';
33
import { block } from '../../reactivity/effects.js';
4-
import { HYDRATION_START } from '../../../../constants.js';
4+
import { HYDRATION_END, HYDRATION_START } from '../../../../constants.js';
5+
6+
/**
7+
* @type {Node | undefined}
8+
*/
9+
let head_anchor;
10+
11+
export function reset_head_anchor() {
12+
head_anchor = undefined;
13+
}
514

615
/**
716
* @param {(anchor: Node) => import('#client').Dom | void} render_fn
@@ -19,12 +28,20 @@ export function head(render_fn) {
1928
if (hydrating) {
2029
previous_hydrate_nodes = hydrate_nodes;
2130

22-
let anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild);
23-
while (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== HYDRATION_START) {
24-
anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling);
31+
// There might be multiple head blocks in our app, so we need to account for each one needing independent hydration.
32+
if (head_anchor === undefined) {
33+
head_anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild);
34+
}
35+
36+
while (
37+
head_anchor.nodeType !== 8 ||
38+
/** @type {Comment} */ (head_anchor).data !== HYDRATION_START
39+
) {
40+
head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling);
2541
}
2642

27-
anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(anchor));
43+
head_anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(head_anchor));
44+
head_anchor = /** @type {import('#client').TemplateNode} */ (head_anchor.nextSibling);
2845
} else {
2946
anchor = document.head.appendChild(empty());
3047
}

packages/svelte/src/internal/client/render.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from './dom/hydration.js';
1919
import { array_from } from './utils.js';
2020
import { handle_event_propagation } from './dom/elements/events.js';
21+
import { reset_head_anchor } from './dom/blocks/svelte-head.js';
2122

2223
/** @type {Set<string>} */
2324
export const all_registered_events = new Set();
@@ -175,6 +176,7 @@ export function hydrate(component, options) {
175176
} finally {
176177
set_hydrating(!!previous_hydrate_nodes);
177178
set_hydrate_nodes(previous_hydrate_nodes);
179+
reset_head_anchor();
178180
}
179181
}
180182

packages/svelte/src/internal/server/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,7 @@ export function render(component, options) {
207207
on_destroy = prev_on_destroy;
208208

209209
return {
210-
head:
211-
payload.head.out || payload.head.title
212-
? payload.head.title + BLOCK_OPEN + payload.head.out + BLOCK_CLOSE
213-
: '',
210+
head: payload.head.out || payload.head.title ? payload.head.out + payload.head.title : '',
214211
html: payload.out
215212
};
216213
}
@@ -247,7 +244,9 @@ export function escape(value, is_attr = false) {
247244
*/
248245
export function head(payload, fn) {
249246
const head_payload = payload.head;
247+
payload.head.out += BLOCK_OPEN;
250248
fn(head_payload);
249+
payload.head.out += BLOCK_CLOSE;
251250
}
252251

253252
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let title = $state('Hello world');
3+
let desc = $state('Some description');
4+
</script>
5+
<svelte:head>
6+
<title>{title}</title>
7+
<meta name="description" content={desc}>
8+
<meta name="author" content="@svelteawesome">
9+
</svelte:head>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `<div>Hello</div>`,
5+
6+
async test({ assert, target }) {
7+
assert.htmlEqual(
8+
target.ownerDocument.head.innerHTML,
9+
`<script async="" src="https://www.googletagmanager.com/gtag/js?id=12345"></script><meta content="Some description" name="description"><meta content="@svelteawesome" name="author"><title>Hello world</title>`
10+
);
11+
}
12+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import MetaTag from './MetaTag.svelte'
3+
</script>
4+
<svelte:head>
5+
<script async src="https://www.googletagmanager.com/gtag/js?id=12345"></script>
6+
</svelte:head>
7+
<MetaTag />
8+
9+
<div>Hello</div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!ssr:0>
2-
<title>Some Title</title>
32
<link rel="canonical" href="/">
43
<meta name="description" content="some description">
54
<meta name="keywords" content="some keywords">
5+
<title>Some Title</title>
66
<!ssr:0>

0 commit comments

Comments
 (0)