Repository: sveltejs/svelte (2025-08-02T05:41:11Z)

Issue:
await doesn't work with @const
### Describe the bug

await doesn't work when used in conjunction with `@const`

### Reproduction

[REPL](https://svelte.dev/playground/hello-world?version=5.36.12#H4sIAAAAAAAAA22OsY7CMBBEf8Xaa4KEoKKxAN11lPRA4eABWXLWlr0hoCj_jkNSneh2nmZmpyc2DUjTAd4H1YXkrapgncAuaEk355FJn3qSVxx9Iyh8Tv3FuMoPeBlZbTK-8WtgAUupoe0EdR1atia99mc-S_-T2cUIURFsHd-rxfDh65lP6rf0ZFHcNjWS2inTGSfqmELjMlYJOfgHqs2cnWzl3q7__yyTBE8hLanFcCnKON85tqRvxmcMb-ZNEVkVAQAA)

### Logs

```shell
await isn't allowed in non-async function (Note that you need plugins to import files that are not JavaScript)
```

### System Info

```shell
Using Svelte compiler version 5.36.12
running Svelte compiler version 5.36.12
```

### Severity

blocking an upgrade


Discussion:
Still not working



Patch:
diff --git a/.changeset/brave-baboons-teach.md b/.changeset/brave-baboons-teach.md
new file mode 100644
index 000000000000..55d7d2a9884f
--- /dev/null
+++ b/.changeset/brave-baboons-teach.md
@@ -0,0 +1,5 @@
+---
+'svelte': minor
+---
+
+feat: allow `await` inside `@const` declarations
diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/create.js b/packages/svelte/src/compiler/phases/1-parse/utils/create.js
index 6030f1bd7bff..2fba918f20ee 100644
--- a/packages/svelte/src/compiler/phases/1-parse/utils/create.js
+++ b/packages/svelte/src/compiler/phases/1-parse/utils/create.js
@@ -10,7 +10,8 @@ export function create_fragment(transparent = false) {
 		nodes: [],
 		metadata: {
 			transparent,
-			dynamic: false
+			dynamic: false,
+			has_await: false
 		}
 	};
 }
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index ecc843d2f42a..1a7a23370d1a 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -37,6 +37,7 @@ import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
 import { ExportSpecifier } from './visitors/ExportSpecifier.js';
 import { ExpressionStatement } from './visitors/ExpressionStatement.js';
 import { ExpressionTag } from './visitors/ExpressionTag.js';
+import { Fragment } from './visitors/Fragment.js';
 import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
 import { FunctionExpression } from './visitors/FunctionExpression.js';
 import { HtmlTag } from './visitors/HtmlTag.js';
@@ -156,6 +157,7 @@ const visitors = {
 	ExportSpecifier,
 	ExpressionStatement,
 	ExpressionTag,
+	Fragment,
 	FunctionDeclaration,
 	FunctionExpression,
 	HtmlTag,
@@ -300,6 +302,7 @@ export function analyze_module(source, options) {
 			function_depth: 0,
 			has_props_rune: false,
 			options: /** @type {ValidatedCompileOptions} */ (options),
+			fragment: null,
 			parent_element: null,
 			reactive_statement: null
 		},
@@ -687,6 +690,7 @@ export function analyze_component(root, source, options) {
 				analysis,
 				options,
 				ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
+				fragment: ast === template.ast ? ast : null,
 				parent_element: null,
 				has_props_rune: false,
 				component_slots: new Set(),
@@ -752,6 +756,7 @@ export function analyze_component(root, source, options) {
 				scopes,
 				analysis,
 				options,
+				fragment: ast === template.ast ? ast : null,
 				parent_element: null,
 				has_props_rune: false,
 				ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts
index 080239bac063..2d99a2e155f6 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts
+++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts
@@ -8,6 +8,7 @@ export interface AnalysisState {
 	analysis: ComponentAnalysis;
 	options: ValidatedCompileOptions;
 	ast_type: 'instance' | 'template' | 'module';
+	fragment: AST.Fragment | null;
 	/**
 	 * Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root.
 	 * Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between.
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js
index af7d0307e9dc..b2f59b849b42 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js
@@ -11,6 +11,15 @@ export function AwaitExpression(node, context) {
 
 	if (context.state.expression) {
 		context.state.expression.has_await = true;
+
+		if (
+			context.state.fragment &&
+			// TODO there's probably a better way to do this
+			context.path.some((node) => node.type === 'ConstTag')
+		) {
+			context.state.fragment.metadata.has_await = true;
+		}
+
 		suspend = true;
 	}
 
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Fragment.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Fragment.js
new file mode 100644
index 000000000000..02d780dc0dc7
--- /dev/null
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Fragment.js
@@ -0,0 +1,10 @@
+/** @import { AST } from '#compiler' */
+/** @import { Context } from '../types.js' */
+
+/**
+ * @param {AST.Fragment} node
+ * @param {Context} context
+ */
+export function Fragment(node, context) {
+	context.next({ ...context.state, fragment: node });
+}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index 0737f531a5b4..166207f66aa7 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -172,6 +172,7 @@ export function client_component(analysis, options) {
 
 		// these are set inside the `Fragment` visitor, and cannot be used until then
 		init: /** @type {any} */ (null),
+		consts: /** @type {any} */ (null),
 		update: /** @type {any} */ (null),
 		after_update: /** @type {any} */ (null),
 		template: /** @type {any} */ (null),
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
index e691be169b55..59c024dfb78b 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
+++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
@@ -6,7 +6,8 @@ import type {
 	Expression,
 	AssignmentExpression,
 	UpdateExpression,
-	VariableDeclaration
+	VariableDeclaration,
+	Declaration
 } from 'estree';
 import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
 import type { TransformState } from '../types.js';
@@ -57,6 +58,8 @@ export interface ComponentClientTransformState extends ClientTransformState {
 	readonly update: Statement[];
 	/** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */
 	readonly after_update: Statement[];
+	/** Transformed `{@const }` declarations */
+	readonly consts: Statement[];
 	/** Memoized expressions */
 	readonly memoizer: Memoizer;
 	/** The HTML template string */
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 6d9dac8a33d3..19a4342b5eb1 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -1,4 +1,4 @@
-/** @import { ArrowFunctionExpression, AssignmentExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
+/** @import { ArrowFunctionExpression, AssignmentExpression, BlockStatement, Expression, FunctionDeclaration, FunctionExpression, Identifier, Node, Pattern, UpdateExpression } from 'estree' */
 /** @import { Binding } from '#compiler' */
 /** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
 /** @import { Analysis } from '../../types.js' */
@@ -289,8 +289,15 @@ export function should_proxy(node, scope) {
 /**
  * Svelte legacy mode should use safe equals in most places, runes mode shouldn't
  * @param {ComponentClientTransformState} state
- * @param {Expression} arg
+ * @param {Expression | BlockStatement} expression
+ * @param {boolean} [async]
  */
-export function create_derived(state, arg) {
-	return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
+export function create_derived(state, expression, async = false) {
+	const thunk = b.thunk(expression, async);
+
+	if (async) {
+		return b.call(b.await(b.call('$.save', b.call('$.async_derived', thunk))));
+	} else {
+		return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', thunk);
+	}
 }
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
index 4246091bcf88..e2e8e93f768c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
@@ -96,13 +96,13 @@ function create_derived_block_argument(node, context) {
 		b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
 	]);
 
-	const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
+	const declarations = [b.var(value, create_derived(context.state, block))];
 
 	for (const id of identifiers) {
 		context.state.transform[id.name] = { read: get_value };
 
 		declarations.push(
-			b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
+			b.var(id, create_derived(context.state, b.member(b.call('$.get', value), id)))
 		);
 	}
 
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js
index 34acdd6bb975..b550dae890db 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js
@@ -16,21 +16,26 @@ export function ConstTag(node, context) {
 	const declaration = node.declaration.declarations[0];
 	// TODO we can almost certainly share some code with $derived(...)
 	if (declaration.id.type === 'Identifier') {
-		const init = build_expression(context, declaration.init, node.metadata.expression);
-		let expression = create_derived(context.state, b.thunk(init));
+		const init = build_expression(
+			{ ...context, state: { ...context.state, in_derived: true } },
+			declaration.init,
+			node.metadata.expression
+		);
+
+		let expression = create_derived(context.state, init, node.metadata.expression.has_await);
 
 		if (dev) {
 			expression = b.call('$.tag', expression, b.literal(declaration.id.name));
 		}
 
-		context.state.init.push(b.const(declaration.id, expression));
+		context.state.consts.push(b.const(declaration.id, expression));
 
 		context.state.transform[declaration.id.name] = { read: get_value };
 
 		// we need to eagerly evaluate the expression in order to hit any
 		// 'Cannot access x before initialization' errors
 		if (dev) {
-			context.state.init.push(b.stmt(b.call('$.get', declaration.id)));
+			context.state.consts.push(b.stmt(b.call('$.get', declaration.id)));
 		}
 	} else {
 		const identifiers = extract_identifiers(declaration.id);
@@ -44,7 +49,11 @@ export function ConstTag(node, context) {
 			delete transform[node.name];
 		}
 
-		const child_state = { ...context.state, transform };
+		const child_state = /** @type {ComponentContext['state']} */ ({
+			...context.state,
+			transform,
+			in_derived: true
+		});
 
 		// TODO optimise the simple `{ x } = y` case — we can just return `y`
 		// instead of destructuring it only to return a new object
@@ -53,26 +62,24 @@ export function ConstTag(node, context) {
 			declaration.init,
 			node.metadata.expression
 		);
-		const fn = b.arrow(
-			[],
-			b.block([
-				b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init),
-				b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
-			])
-		);
 
-		let expression = create_derived(context.state, fn);
+		const block = b.block([
+			b.const(/** @type {Pattern} */ (context.visit(declaration.id, child_state)), init),
+			b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
+		]);
+
+		let expression = create_derived(context.state, block, node.metadata.expression.has_await);
 
 		if (dev) {
 			expression = b.call('$.tag', expression, b.literal('[@const]'));
 		}
 
-		context.state.init.push(b.const(tmp, expression));
+		context.state.consts.push(b.const(tmp, expression));
 
 		// we need to eagerly evaluate the expression in order to hit any
 		// 'Cannot access x before initialization' errors
 		if (dev) {
-			context.state.init.push(b.stmt(b.call('$.get', tmp)));
+			context.state.consts.push(b.stmt(b.call('$.get', tmp)));
 		}
 
 		for (const node of identifiers) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js
index 0b10c02ffbe1..c7c576101e5d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js
@@ -48,8 +48,10 @@ export function Fragment(node, context) {
 	const is_single_child_not_needing_template =
 		trimmed.length === 1 &&
 		(trimmed[0].type === 'SvelteFragment' || trimmed[0].type === 'TitleElement');
+	const has_await = context.state.init !== null && (node.metadata.has_await || false);
 
 	const template_name = context.state.scope.root.unique('root'); // TODO infer name from parent
+	const unsuspend = b.id('$$unsuspend');
 
 	/** @type {Statement[]} */
 	const body = [];
@@ -61,6 +63,7 @@ export function Fragment(node, context) {
 	const state = {
 		...context.state,
 		init: [],
+		consts: [],
 		update: [],
 		after_update: [],
 		memoizer: new Memoizer(),
@@ -76,11 +79,6 @@ export function Fragment(node, context) {
 		context.visit(node, state);
 	}
 
-	if (is_text_first) {
-		// skip over inserted comment
-		body.push(b.stmt(b.call('$.next')));
-	}
-
 	if (is_single_element) {
 		const element = /** @type {AST.RegularElement} */ (trimmed[0]);
 
@@ -96,13 +94,13 @@ export function Fragment(node, context) {
 		const template = transform_template(state, namespace, flags);
 		state.hoisted.push(b.var(template_name, template));
 
-		body.push(b.var(id, b.call(template_name)));
+		state.init.unshift(b.var(id, b.call(template_name)));
 		close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
 	} else if (is_single_child_not_needing_template) {
 		context.visit(trimmed[0], state);
 	} else if (trimmed.length === 1 && trimmed[0].type === 'Text') {
 		const id = b.id(context.state.scope.generate('text'));
-		body.push(b.var(id, b.call('$.text', b.literal(trimmed[0].data))));
+		state.init.unshift(b.var(id, b.call('$.text', b.literal(trimmed[0].data))));
 		close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
 	} else if (trimmed.length > 0) {
 		const id = b.id(context.state.scope.generate('fragment'));
@@ -120,7 +118,7 @@ export function Fragment(node, context) {
 				state
 			});
 
-			body.push(b.var(id, b.call('$.text')));
+			state.init.unshift(b.var(id, b.call('$.text')));
 			close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
 		} else {
 			if (is_standalone) {
@@ -140,12 +138,12 @@ export function Fragment(node, context) {
 
 				if (state.template.nodes.length === 1 && state.template.nodes[0].type === 'comment') {
 					// special case — we can use `$.comment` instead of creating a unique template
-					body.push(b.var(id, b.call('$.comment')));
+					state.init.unshift(b.var(id, b.call('$.comment')));
 				} else {
 					const template = transform_template(state, namespace, flags);
 					state.hoisted.push(b.var(template_name, template));
 
-					body.push(b.var(id, b.call(template_name)));
+					state.init.unshift(b.var(id, b.call(template_name)));
 				}
 
 				close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
@@ -153,6 +151,21 @@ export function Fragment(node, context) {
 		}
 	}
 
+	if (has_await) {
+		body.push(b.var(unsuspend, b.call('$.suspend')));
+	}
+
+	body.push(...state.consts);
+
+	if (has_await) {
+		body.push(b.if(b.call('$.aborted'), b.return()));
+	}
+
+	if (is_text_first) {
+		// skip over inserted comment
+		body.push(b.stmt(b.call('$.next')));
+	}
+
 	body.push(...state.init);
 
 	if (state.update.length > 0) {
@@ -168,5 +181,9 @@ export function Fragment(node, context) {
 		body.push(close);
 	}
 
+	if (has_await) {
+		body.push(b.stmt(b.call(unsuspend)));
+	}
+
 	return b.block(body);
 }
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
index abdbc381d99c..f33febeeb281 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
@@ -46,9 +46,6 @@ export function LetDirective(node, context) {
 			read: (node) => b.call('$.get', node)
 		};
 
-		return b.const(
-			name,
-			create_derived(context.state, b.thunk(b.member(b.id('$$slotProps'), node.name)))
-		);
+		return b.const(name, create_derived(context.state, b.member(b.id('$$slotProps'), node.name)));
 	}
 }
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js
index 203cf62b3792..895522d47ab2 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js
@@ -14,6 +14,7 @@ export function SnippetBlock(node, context) {
 	// TODO hoist where possible
 	/** @type {(Identifier | AssignmentPattern)[]} */
 	const args = [b.id('$$anchor')];
+	const has_await = node.body.metadata.has_await || false;
 
 	/** @type {BlockStatement} */
 	let body;
@@ -21,10 +22,6 @@ export function SnippetBlock(node, context) {
 	/** @type {Statement[]} */
 	const declarations = [];
 
-	if (dev) {
-		declarations.push(b.stmt(b.call('$.validate_snippet_args', b.spread(b.id('arguments')))));
-	}
-
 	const transform = { ...context.state.transform };
 	const child_state = { ...context.state, transform };
 
@@ -72,16 +69,21 @@ export function SnippetBlock(node, context) {
 			}
 		}
 	}
-
+	const block = /** @type {BlockStatement} */ (context.visit(node.body, child_state)).body;
 	body = b.block([
+		dev ? b.stmt(b.call('$.validate_snippet_args', b.spread(b.id('arguments')))) : b.empty,
 		...declarations,
-		.../** @type {BlockStatement} */ (context.visit(node.body, child_state)).body
+		...block
 	]);
 
 	// in dev we use a FunctionExpression (not arrow function) so we can use `arguments`
 	let snippet = dev
-		? b.call('$.wrap_snippet', b.id(context.state.analysis.name), b.function(null, args, body))
-		: b.arrow(args, body);
+		? b.call(
+				'$.wrap_snippet',
+				b.id(context.state.analysis.name),
+				b.function(null, args, body, has_await)
+			)
+		: b.arrow(args, body, has_await);
 
 	const declaration = b.const(node.expression, snippet);
 
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js
index d37b990440c7..70df0223557d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js
@@ -43,7 +43,7 @@ export function SvelteBoundary(node, context) {
 	// to resolve this we cheat: we duplicate const tags inside snippets
 	for (const child of node.fragment.nodes) {
 		if (child.type === 'ConstTag') {
-			context.visit(child, { ...context.state, init: const_tags });
+			context.visit(child, { ...context.state, consts: const_tags });
 		}
 	}
 
diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts
index 058a1a8e66c3..060df2dcb2a4 100644
--- a/packages/svelte/src/compiler/types/template.d.ts
+++ b/packages/svelte/src/compiler/types/template.d.ts
@@ -56,6 +56,7 @@ export namespace AST {
 			 * Whether or not we need to traverse into the fragment during mount/hydrate
 			 */
 			dynamic: boolean;
+			has_await: boolean;
 		};
 	}
 
diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js
index defdb1642e1d..56a5f31ffe82 100644
--- a/packages/svelte/src/compiler/utils/builders.js
+++ b/packages/svelte/src/compiler/utils/builders.js
@@ -588,14 +588,14 @@ export function method(kind, key, params, body, computed = false, is_static = fa
  * @param {ESTree.BlockStatement} body
  * @returns {ESTree.FunctionExpression}
  */
-function function_builder(id, params, body) {
+function function_builder(id, params, body, async = false) {
 	return {
 		type: 'FunctionExpression',
 		id,
 		params,
 		body,
 		generator: false,
-		async: false,
+		async,
 		metadata: /** @type {any} */ (null) // should not be used by codegen
 	};
 }


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/_config.js b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js
new file mode 100644
index 000000000000..084d9c3874ef
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-const/_config.js
@@ -0,0 +1,12 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+	html: `<h1>Loading...</h1>`,
+
+	async test({ assert, target }) {
+		await tick();
+
+		assert.htmlEqual(target.innerHTML, `<h1>Hello, world!</h1>`);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte
new file mode 100644
index 000000000000..9321bd792980
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-const/main.svelte
@@ -0,0 +1,16 @@
+<script>
+	let name = $state('world');
+</script>
+
+<svelte:boundary>
+	{#snippet pending()}
+		<h1>Loading...</h1>
+	{/snippet}
+
+	{#snippet greet()}
+		{@const greeting = await `Hello, ${name}!`}
+		<h1>{greeting}</h1>
+	{/snippet}
+
+	{@render greet()}
+</svelte:boundary>