diff --git a/packages/router/.size-limit.json b/packages/router/.size-limit.json
index e9ad0285..2e8fe992 100644
--- a/packages/router/.size-limit.json
+++ b/packages/router/.size-limit.json
@@ -4,7 +4,7 @@
"gzip": true,
"path": "dist/index.production.js",
"import": "*",
- "limit": "4.15 kB"
+ "limit": "4.2 kB"
},
{
"name": "All publics (Brotli)",
diff --git a/packages/router/src/head.spec.ts b/packages/router/src/head.spec.ts
index a0d3028f..6ffbda11 100644
--- a/packages/router/src/head.spec.ts
+++ b/packages/router/src/head.spec.ts
@@ -16,11 +16,23 @@ import {
dir,
link,
meta,
- script
+ script,
+ toHtmlAttribute
} from './head.js'
describe('router', () => {
describe('head', () => {
+ describe('toHtmlAttribute', () => {
+ it('should map httpEquiv to http-equiv', () => {
+ expect(toHtmlAttribute('httpEquiv')).toBe('http-equiv')
+ })
+
+ it('should lowercase other prop names', () => {
+ expect(toHtmlAttribute('crossOrigin')).toBe('crossorigin')
+ expect(toHtmlAttribute('content')).toBe('content')
+ })
+ })
+
describe('syncHead', () => {
describe('title', () => {
beforeEach(() => {
@@ -589,6 +601,36 @@ describe('router', () => {
stop()
})
+ it('should render httpEquiv as the http-equiv attribute', () => {
+ const stop = syncHead(() => ({
+ default: null,
+ Head$: () => [meta({
+ httpEquiv: 'refresh',
+ content: '0; url=/next'
+ })]
+ }))
+
+ expect(document.head.innerHTML).toBe('')
+
+ stop()
+ })
+
+ it('should hydrate existing http-equiv meta tag', () => {
+ document.head.innerHTML = ''
+
+ const stop = syncHead(() => ({
+ default: null,
+ Head$: () => [meta({
+ httpEquiv: 'refresh',
+ content: '0; url=/next'
+ })]
+ }))
+
+ expect(document.head.innerHTML).toBe('')
+
+ stop()
+ })
+
it('should hydrate existing meta tag', () => {
document.head.innerHTML = ''
diff --git a/packages/router/src/head.ts b/packages/router/src/head.ts
index c956a5fc..4b49199c 100644
--- a/packages/router/src/head.ts
+++ b/packages/router/src/head.ts
@@ -113,6 +113,16 @@ export function dir($value: DirValue): DirPropertyDescriptor {
}
}
+/**
+ * Converts a head descriptor prop name to its HTML attribute name.
+ * @param prop - Prop name, such as `httpEquiv` or `crossOrigin`.
+ * @returns The attribute name, such as `http-equiv` or `crossorigin`.
+ */
+/* @__NO_SIDE_EFFECTS__ */
+export function toHtmlAttribute(prop: string) {
+ return prop === 'httpEquiv' ? 'http-equiv' : prop.toLowerCase()
+}
+
function buildPredicate(tag: string, attributes: [string, AnySignalish][]) {
let selector = tag
let count = 0
@@ -126,7 +136,7 @@ function buildPredicate(tag: string, attributes: [string, AnySignalish][]) {
code = String(resolvedValue)
} else {
count++
- selector += `[${key}="${String(resolvedValue).replace(/"/g, '\\"')}"]`
+ selector += `[${toHtmlAttribute(key)}="${String(resolvedValue).replace(/"/g, '\\"')}"]`
}
}
}
@@ -188,7 +198,7 @@ function startElement(
if (key === 'code') {
element.textContent = stringValue
} else {
- element.setAttribute(key, stringValue)
+ element.setAttribute(toHtmlAttribute(key), stringValue)
}
}
}
diff --git a/packages/ssr/src/renderer/utils.spec.ts b/packages/ssr/src/renderer/utils.spec.ts
index 028eb06e..5de56ca0 100644
--- a/packages/ssr/src/renderer/utils.spec.ts
+++ b/packages/ssr/src/renderer/utils.spec.ts
@@ -48,6 +48,13 @@ describe('ssr', () => {
}))).toBe('')
})
+ it('should render httpEquiv as the http-equiv attribute', () => {
+ expect(headDescriptorToHtml(meta({
+ httpEquiv: 'refresh',
+ content: '0; url=/next'
+ }))).toBe('')
+ })
+
it('should insert script code as is', () => {
expect(headDescriptorToHtml(script({
type: 'application/ld+json',
diff --git a/packages/ssr/src/renderer/utils.ts b/packages/ssr/src/renderer/utils.ts
index 81125b73..a9932b0d 100644
--- a/packages/ssr/src/renderer/utils.ts
+++ b/packages/ssr/src/renderer/utils.ts
@@ -2,7 +2,8 @@ import {
type HeadDescriptor,
type Location,
type PageRef,
- PermanentReplaceHistoryAction
+ PermanentReplaceHistoryAction,
+ toHtmlAttribute
} from '@nano_kit/router'
import {
type EmptyValue,
@@ -84,7 +85,7 @@ export function headDescriptorToHtml(descriptor: HeadDescriptor): string {
if (key === 'code') {
code = String(resolvedValue)
} else {
- html += ` ${key.toLowerCase()}="${escapeHtml(String(resolvedValue))}"`
+ html += ` ${toHtmlAttribute(key)}="${escapeHtml(String(resolvedValue))}"`
}
}
})