Skip to content

fix(astro): Ensure runInjectionScript runs on initial page loads with view transitions enabled#7801

Merged
jacekradko merged 5 commits intoclerk:mainfrom
lino-levan:lino/fix-astro-components-integration
Feb 9, 2026
Merged

fix(astro): Ensure runInjectionScript runs on initial page loads with view transitions enabled#7801
jacekradko merged 5 commits intoclerk:mainfrom
lino-levan:lino/fix-astro-components-integration

Conversation

@lino-levan
Copy link
Contributor

@lino-levan lino-levan commented Feb 9, 2026

Description

At work we have an Astro site using Clerk and we had this issue in dev where Clerk's components never appeared until a page navigation happened. It took a while to track down where the issue was, and eventually we saw that the dynamic import of the transitions was racing against the astro:page-load event. This PR is from the patch that is currently in our production codebase.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.

Type of change

  • 🐛 Bug fix

Summary by CodeRabbit

  • Bug Fixes
    • Resolved a timing issue in page transitions and navigation so components load reliably during navigation.
  • Chores
    • Added a changeset entry documenting a patch release for the fix.

@vercel
Copy link

vercel bot commented Feb 9, 2026

@lino-levan is attempting to deploy a commit to the Clerk Production Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link

changeset-bot bot commented Feb 9, 2026

🦋 Changeset detected

Latest commit: eaa1bc4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/astro Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This change moves dynamic imports of astro:transitions/client in the create-integration module from top-level page script initialization into event listeners. The astro:before-swap handler is now async and dynamically imports swapFunctions before running swap logic. The astro:page-load handler dynamically imports navigate before calling runInjectionScript. These edits change the timing and ordering of transitions-related imports to defer them until the relevant events occur.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely summarizes the main fix: ensuring runInjectionScript executes on initial page loads when view transitions are enabled, which matches the core change of deferring dynamic imports to avoid race conditions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/astro/src/integration/create-integration.ts (1)

123-134: ⚠️ Potential issue | 🔴 Critical

Async astro:before-swap handler will silently break the custom swap logic.

According to Astro's documentation, astro:before-swap listeners run synchronously and Astro does not await async handlers. When you override event.swap = () => { ... }, Astro calls it synchronously — it is not awaited. (Astro View Transitions docs)

In the current code (lines 123–134), the handler is async with an await import(...) at line 124. This causes the function to return a Promise immediately, deferring lines 126–133 (including e.swap = ...) to a microtask. When Astro's dispatchEvent() returns, e.swap has not yet been set, so Astro calls the default swap function instead of your custom one. The Clerk component preservation silently fails.

Move the import outside the listener to keep the handler synchronous:

Proposed fix
             if (transitionEnabledOnThisPage()) {
-              // We must do the dynamic imports within the event listeners because otherwise we may race and miss initial astro:page-load
-              document.addEventListener('astro:before-swap', async (e) => {
-                const { swapFunctions } = await import('astro:transitions/client');
+              const swapFunctionsPromise = import('astro:transitions/client');
+
+              document.addEventListener('astro:before-swap', (e) => {
+                const { swapFunctions } = getSwapFunctionsSync(swapFunctionsPromise);
 
                 const clerkComponents = document.querySelector('#clerk-components');
                 // Keep the div element added by Clerk
                 if (clerkComponents) {
                   const clonedEl = clerkComponents.cloneNode(true);
                   e.newDocument.body.appendChild(clonedEl);
                 }
 
                 e.swap = () => swapDocument(swapFunctions, e.newDocument);
               });

Alternatively, wrap the import() in a WeakMap cache to avoid re-importing:

+              let swapFunctionsCached;
+              import('astro:transitions/client').then(mod => {
+                swapFunctionsCached = mod.swapFunctions;
+              });
+
-              document.addEventListener('astro:before-swap', async (e) => {
-                const { swapFunctions } = await import('astro:transitions/client');
+              document.addEventListener('astro:before-swap', (e) => {
+                if (!swapFunctionsCached) return; // Fallback if import hasn't resolved
 
                 const clerkComponents = document.querySelector('#clerk-components');
                 // Keep the div element added by Clerk
                 if (clerkComponents) {
                   const clonedEl = clerkComponents.cloneNode(true);
                   e.newDocument.body.appendChild(clonedEl);
                 }
 
-                e.swap = () => swapDocument(swapFunctions, e.newDocument);
+                e.swap = () => swapDocument(swapFunctionsCached, e.newDocument);
               });

The module will be cached by the time the first before-swap fires, since astro:transitions/client is loaded by Astro's runtime.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.changeset/ready-cats-tease.md:
- Line 5: Change the changelog line "Fixed issue when using ClientRouter where
clerk components don't load until navigation is performed" to a polished
sentence with proper capitalization and punctuation, e.g. "Fixed an issue where
Clerk components didn’t load on initial page load when using ClientRouter."
Ensure "Clerk" is capitalized and use the improved wording that mentions
"initial page load" instead of "until navigation is performed."

'@clerk/astro': patch
---

Fixed issue when using ClientRouter where clerk components don't load until navigation is performed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Polish changelog wording/capitalization.

Consider “Clerk” capitalization and a slightly clearer sentence, e.g., “Fixed an issue where Clerk components didn’t load on initial page load when using ClientRouter.”

🤖 Prompt for AI Agents
In @.changeset/ready-cats-tease.md at line 5, Change the changelog line "Fixed
issue when using ClientRouter where clerk components don't load until navigation
is performed" to a polished sentence with proper capitalization and punctuation,
e.g. "Fixed an issue where Clerk components didn’t load on initial page load
when using ClientRouter." Ensure "Clerk" is capitalized and use the improved
wording that mentions "initial page load" instead of "until navigation is
performed."

@wobsoriano wobsoriano self-requested a review February 9, 2026 04:23
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 9, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7801

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7801

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7801

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7801

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7801

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7801

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7801

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7801

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7801

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7801

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7801

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7801

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7801

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7801

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7801

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7801

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7801

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7801

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7801

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7801

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7801

commit: accf7bd

Copy link
Member

@wobsoriano wobsoriano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, thanks!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/astro/src/integration/create-integration.ts (1)

129-140: ⚠️ Potential issue | 🔴 Critical

The async before-swap handler will not work — e.swap and e.newDocument modifications are assigned in a microtask after the swap is already dispatched.

dispatchEvent() is synchronous and returns immediately after calling all listeners. Any async listener runs synchronously only up to its first await, then the rest continues in a microtask. This means:

  1. Line 130: await import(...) suspends the listener
  2. Astro's router continues and calls e.swap() synchronously during the dispatch
  3. Lines 136–139: Execute later in the microtask queue — after e.swap() has already been invoked

The result: the Clerk component clone and custom swap function are never used.

Per Astro's guidance, astro:before-swap work must remain strictly synchronous and fast. Moving the import inside the listener doesn't solve the original race condition mentioned in the comment; it worsens timing precision.

The astro:page-load handler (lines 142–150) is safe to be async since that event is informational.

Fix: Use module-scoped state to resolve the imports during page-load (which is safe to be async) and read them synchronously in before-swap:

Proposed fix
              let swapFunctions;
              let navigateFn;

              document.addEventListener('astro:before-swap', (e) => {
+               if (!swapFunctions) return;
                const clerkComponents = document.querySelector('#clerk-components');
                if (clerkComponents) {
                  const clonedEl = clerkComponents.cloneNode(true);
                  e.newDocument.body.appendChild(clonedEl);
                }
                e.swap = () => swapDocument(swapFunctions, e.newDocument);
              });

              document.addEventListener('astro:page-load', async (e) => {
-               const { navigate } = await import('astro:transitions/client');
+               const client = await import('astro:transitions/client');
+               swapFunctions = client.swapFunctions;
+               navigateFn = client.navigate;

                await runInjectionScript({
                  ...${JSON.stringify(internalParams)},
-                 routerPush: navigate,
-                 routerReplace: (url) => navigate(url, { history: 'replace' }),
+                 routerPush: navigateFn,
+                 routerReplace: (url) => navigateFn(url, { history: 'replace' }),
                });
              })

@alexcarpenter alexcarpenter changed the title fix(astro): runInjectionScript not running on initial page loads with view transitions enabled fix(astro): Ensure runInjectionScript runs on initial page loads with view transitions enabled Feb 9, 2026
@wobsoriano wobsoriano requested a review from jacekradko February 9, 2026 15:33
@jacekradko jacekradko merged commit 2c74569 into clerk:main Feb 9, 2026
12 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants