diff --git a/.changeset/ssr-empty-boolean-attribute.md b/.changeset/ssr-empty-boolean-attribute.md
new file mode 100644
index 0000000000..dbf5465014
--- /dev/null
+++ b/.changeset/ssr-empty-boolean-attribute.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: keep boolean attributes with an empty string value when rendering attribute objects on the server
diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js
index bb1abf22c1..68994bf85e 100644
--- a/packages/svelte/src/internal/shared/attributes.js
+++ b/packages/svelte/src/internal/shared/attributes.js
@@ -27,7 +27,8 @@ export function attr(name, value, is_boolean = false) {
if (name === 'hidden' && value !== 'until-found') {
is_boolean = true;
}
- if (value == null || (!value && is_boolean)) return '';
+ // `''` is a present boolean attribute, as it is in markup and on the client
+ if (value == null || (is_boolean && !value && value !== '')) return '';
const normalized =
(has_own_property.call(replacements, name) && replacements[name].get(value)) || value;
const assignment = is_boolean ? `=""` : `="${escape_html(normalized, true)}"`;
diff --git a/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/_expected.html b/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/_expected.html
new file mode 100644
index 0000000000..0c9cc0cd8c
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/_expected.html
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/main.svelte b/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/main.svelte
new file mode 100644
index 0000000000..25af2b774d
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/empty-boolean-attribute-object/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
+