Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shallow-routing-initial-effect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: allow `pushState` and `replaceState` to be called during initial render
16 changes: 12 additions & 4 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ let current = {
/** this being true means we SSR'd */
let hydrated = false;
let started = false;
/** True once shallow routing can be used, which is before `started` since effects run during initial render (#15927) */
let router_initialized = false;
let autoscroll = true;
let updating = false;
let is_navigating = false;
Expand Down Expand Up @@ -719,6 +721,10 @@ async function initialize(result, target, hydrate) {
update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
current_tree = result.props.tree;

// effects flush synchronously while the root component is created below,
// so anything shallow routing needs must be ready before this point
router_initialized = true;

// TODO: use mount()
root = new Root({
target,
Expand Down Expand Up @@ -2572,7 +2578,7 @@ export function pushState(url, state) {
}

if (DEV) {
if (!started) {
if (!router_initialized) {
throw new Error('Cannot call pushState(...) before router is initialized');
}

Expand All @@ -2590,7 +2596,8 @@ export function pushState(url, state) {
const opts = {
[HISTORY_INDEX]: (current_history_index += 1),
[NAVIGATION_INDEX]: current_navigation_index,
[PAGE_URL_KEY]: page.url.href,
// untracked so that effects calling this function don't depend on `page.url`
[PAGE_URL_KEY]: svelte.untrack(() => page.url.href),
[STATES_KEY]: state
};

Expand All @@ -2615,7 +2622,7 @@ export function replaceState(url, state) {
}

if (DEV) {
if (!started) {
if (!router_initialized) {
throw new Error('Cannot call replaceState(...) before router is initialized');
}

Expand All @@ -2631,7 +2638,8 @@ export function replaceState(url, state) {
const opts = {
[HISTORY_INDEX]: current_history_index,
[NAVIGATION_INDEX]: current_navigation_index,
[PAGE_URL_KEY]: page.url.href,
// untracked so that effects calling this function don't depend on `page.url`
[PAGE_URL_KEY]: svelte.untrack(() => page.url.href),
[STATES_KEY]: state
};

Expand Down
1 change: 1 addition & 0 deletions packages/kit/test/apps/basics/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare global {
interface PageState {
active?: boolean;
count?: number;
initial?: boolean;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import { pushState } from '$app/navigation';
import { page } from '$app/state';

$effect(() => {
pushState('', { initial: true });
});
</script>

<p>initial: {page.state.initial ?? false}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import { replaceState } from '$app/navigation';
import { page } from '$app/state';

$effect(() => {
replaceState('', { initial: true });
});
</script>

<p>initial: {page.state.initial ?? false}</p>
13 changes: 13 additions & 0 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,7 @@
).toBe('accessible anywhere/evaluated at run time');
});

test('uses correct dynamic env when navigating from prerendered page', async ({

Check warning on line 1276 in packages/kit/test/apps/basics/test/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit (22, ubuntu-latest, chromium, current)

flaky test: uses correct dynamic env when navigating from prerendered page

retries: 2
page,
clicknav
}) => {
Expand Down Expand Up @@ -1801,6 +1801,19 @@
await expect(page.locator('p')).toHaveText('active: false');
await expect(page.locator('span')).not.toHaveText(now);
});

test('pushState can be called in $effect during initial render', async ({ page }) => {
await page.goto('/shallow-routing/push-state/initial-effect');
await expect(page.locator('p')).toHaveText('initial: true');

await page.goBack();
await expect(page.locator('p')).toHaveText('initial: false');
});

test('replaceState can be called in $effect during initial render', async ({ page }) => {
await page.goto('/shallow-routing/replace-state/initial-effect');
await expect(page.locator('p')).toHaveText('initial: true');
});
});

test.describe('reroute', () => {
Expand All @@ -1823,7 +1836,7 @@
);
});

test('Apply async prerendered reroute during client side navigation', async ({ page }) => {

Check warning on line 1839 in packages/kit/test/apps/basics/test/client.test.js

View workflow job for this annotation

GitHub Actions / test-kit (24, ubuntu-latest, chromium, current)

flaky test: Apply async prerendered reroute during client side navigation

retries: 2
await page.goto('/reroute/async');
await page.click("a[href='/reroute/async/c']");
expect(await page.textContent('h1')).toContain(
Expand Down
Loading