Skip to content

Commit cfc465d

Browse files
authored
fix: self link does not trigger page reload (#8182)
1 parent f224c53 commit cfc465d

File tree

4 files changed

+68
-22
lines changed

4 files changed

+68
-22
lines changed

.changeset/gold-carpets-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
View Transitions: self link (`href=""`) does not trigger page reload

packages/astro/components/ViewTransitions.astro

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -274,30 +274,54 @@ const { fallback = 'animate' } = Astro.props as Props;
274274
// that is going to another page within the same origin. Basically it determines
275275
// same-origin navigation, but omits special key combos for new tabs, etc.
276276
if (
277-
link &&
278-
link instanceof HTMLAnchorElement &&
279-
link.href &&
280-
(!link.target || link.target === '_self') &&
281-
link.origin === location.origin &&
282-
!(
283-
// Same page means same path and same query params
284-
(location.pathname === link.pathname && location.search === link.search)
285-
) &&
286-
ev.button === 0 && // left clicks only
287-
!ev.metaKey && // new tab (mac)
288-
!ev.ctrlKey && // new tab (windows)
289-
!ev.altKey && // download
290-
!ev.shiftKey &&
291-
!ev.defaultPrevented &&
292-
transitionEnabledOnThisPage()
293-
) {
294-
ev.preventDefault();
295-
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
296-
const newState: State = { index: currentHistoryIndex, scrollY };
297-
persistState({ index: currentHistoryIndex - 1, scrollY });
298-
history.pushState(newState, '', link.href);
277+
!link ||
278+
!(link instanceof HTMLAnchorElement) ||
279+
!link.href ||
280+
(link.target && link.target !== '_self') ||
281+
link.origin !== location.origin ||
282+
ev.button !== 0 || // left clicks only
283+
ev.metaKey || // new tab (mac)
284+
ev.ctrlKey || // new tab (windows)
285+
ev.altKey || // download
286+
ev.shiftKey || // new window
287+
ev.defaultPrevented ||
288+
!transitionEnabledOnThisPage()
289+
)
290+
// No page transitions in these cases,
291+
// Let the browser standard action handle this
292+
return;
293+
294+
// We do not need to handle same page links because there are no page transitions
295+
// Same page means same path and same query params (but different hash)
296+
if (location.pathname === link.pathname && location.search === link.search) {
297+
if (link.hash) {
298+
// The browser default action will handle navigations with hash fragments
299+
return;
300+
} else {
301+
// Special case: self link without hash
302+
// If handed to the browser it will reload the page
303+
// But we want to handle it like any other same page navigation
304+
// So we scroll to the top of the page but do not start page transitions
305+
ev.preventDefault();
306+
persistState({ ...history.state, scrollY });
307+
scrollTo({ left: 0, top: 0, behavior: 'instant' });
308+
if (location.hash) {
309+
// last target was different
310+
const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
311+
history.pushState(newState, '', link.href);
312+
}
313+
return;
314+
}
299315
}
316+
317+
// these are the cases we will handle: same origin, different page
318+
ev.preventDefault();
319+
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
320+
const newState: State = { index: currentHistoryIndex, scrollY };
321+
persistState({ index: currentHistoryIndex - 1, scrollY });
322+
history.pushState(newState, '', link.href);
300323
});
324+
301325
addEventListener('popstate', (ev) => {
302326
if (!transitionEnabledOnThisPage()) {
303327
// The current page doesn't haven't View Transitions,

packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Layout from '../components/Layout.astro';
77
<a id="click-two" href="/two">go to 2</a>
88
<a id="click-three" href="/three">go to 3</a>
99
<a id="click-longpage" href="/long-page">go to long page</a>
10+
<a id="click-self" href="">go to top</a>
1011

1112
<div id="test">test content</div>
1213
</Layout>

packages/astro/e2e/view-transitions.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ test.describe('View Transitions', () => {
190190
await expect(p, 'should have content').toHaveText('Page 1');
191191
});
192192

193+
test('click self link (w/o hash) does not do navigation', async ({ page, astro }) => {
194+
const loads = [];
195+
page.addListener('load', (p) => {
196+
loads.push(p.title());
197+
});
198+
// Go to page 1
199+
await page.goto(astro.resolveUrl('/one'));
200+
const p = page.locator('#one');
201+
await expect(p, 'should have content').toHaveText('Page 1');
202+
203+
// Clicking href="" stays on page
204+
await page.click('#click-self');
205+
await expect(p, 'should have content').toHaveText('Page 1');
206+
expect(loads.length, 'There should only be 1 page load').toEqual(1);
207+
});
208+
193209
test('Scroll position restored on back button', async ({ page, astro }) => {
194210
// Go to page 1
195211
await page.goto(astro.resolveUrl('/long-page'));

0 commit comments

Comments
 (0)