Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2c7e674
close SearchMenu on client navigation
reidbarber Nov 5, 2025
5cb61c3
add footer
reidbarber Nov 6, 2025
33f0724
made Nav and ToC widths static
reidbarber Nov 6, 2025
35069c4
fix min-height issue on mobile
reidbarber Nov 6, 2025
5e84727
show Toast if route can't load
reidbarber Nov 6, 2025
9122573
add toast for clipboard copy failures
reidbarber Nov 6, 2025
45ec5f7
fix React insertion effect
reidbarber Nov 6, 2025
81f27fb
improve markdown menu
reidbarber Nov 6, 2025
c35adb7
add skeleton loading for client routing
reidbarber Nov 6, 2025
c7b2c20
copy update
reidbarber Nov 6, 2025
217f3df
Merge remote-tracking branch 'origin/main' into s2-docs-general-impro…
reidbarber Nov 6, 2025
df6fe71
remote extra startTransitions and use use hook
reidbarber Nov 7, 2025
2bbe51f
improve skeleton loading and optimistic render link selection + ToC
reidbarber Nov 7, 2025
93dca54
add delay to showing skeleton
reidbarber Nov 7, 2025
9338497
fix getPageInfo logic
reidbarber Nov 7, 2025
dc3309a
try fixing getPageInfo again
reidbarber Nov 7, 2025
25297d3
try fixing normalizePathname on build
reidbarber Nov 7, 2025
aa6ba15
add prefetch onPressStart
reidbarber Nov 7, 2025
9cd4e02
revert optimistic UI and show error toast if fetch fails
reidbarber Nov 7, 2025
fbd63dd
fix skeleton title
reidbarber Nov 10, 2025
31683a3
don't clear targetPathname until new navigation
reidbarber Nov 10, 2025
4970161
move prefetch to a global pointerover listener
reidbarber Nov 10, 2025
cf27fd3
close search menu when navigation starts
reidbarber Nov 10, 2025
8d77e46
show skeleton loading immediately when navigation starts
reidbarber Nov 10, 2025
de49692
Merge remote-tracking branch 'origin/main' into s2-docs-general-impro…
reidbarber Nov 11, 2025
d971a15
re-use tag group in SearchMenu and MobileSearchMenu
reidbarber Nov 11, 2025
7d2fb80
fix and extract out search logic
reidbarber Nov 11, 2025
5cfdd17
remove blog and releases index pages from search
reidbarber Nov 11, 2025
6c98a92
reduce size of icons in dnd blog post example
reidbarber Nov 11, 2025
537d8b1
fix Forms password example
reidbarber Nov 11, 2025
751fc05
centralize search logic
reidbarber Nov 11, 2025
77dd142
add escapeKeyBehavior="none" to tag groups
reidbarber Nov 11, 2025
0a7dcbc
fix toc scroll mask
reidbarber Nov 11, 2025
f4fd084
simplify pathname logic
reidbarber Nov 12, 2025
67a3b66
remove promises from prefetch cache once resolved
reidbarber Nov 12, 2025
aef8e3b
use useOptimistic
reidbarber Nov 12, 2025
f3b9f25
use fetchRSC promise as loading source
reidbarber Nov 12, 2025
839f0d3
avoid navigation race conditions
reidbarber Nov 12, 2025
80431b0
prefetch on focus (same as pointerover)
reidbarber Nov 12, 2025
512a045
function for checking if client link
reidbarber Nov 12, 2025
90abcd8
fix search results sorting for All tag
reidbarber Nov 12, 2025
b70d7d8
add message to currentAbortController.abort
reidbarber Nov 12, 2025
d01efc1
lint
reidbarber Nov 12, 2025
ff3c90c
Merge remote-tracking branch 'origin/main' into s2-docs-general-impro…
reidbarber Nov 12, 2025
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
47 changes: 46 additions & 1 deletion packages/dev/s2-docs/src/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,27 @@ let updateRoot = hydrate({
}
});

// Track the current navigation to prevent race conditions
let currentNavigationId = 0;
let currentAbortController: AbortController | null = null;

// A very simple router. When we navigate, we'll fetch a new RSC payload from the server,
// and in a React transition, stream in the new page. Once complete, we'll pushState to
// update the URL in the browser.
async function navigate(pathname: string, push = false) {
let [basePath] = pathname.split('#');
let rscPath = basePath.replace('.html', '.rsc');

// Cancel any in-flight navigation
if (currentAbortController) {
currentAbortController.abort();
}

// Create a new abort controller for this navigation
const abortController = new AbortController();
currentAbortController = abortController;
const navigationId = ++currentNavigationId;

const navigationPromise = (async () => {
window.dispatchEvent(new CustomEvent('rsc-navigation-start'));

Expand All @@ -31,6 +45,18 @@ async function navigate(pathname: string, push = false) {

try {
let res = await fetchPromise;

// Check if this navigation is still current before updating
if (navigationId !== currentNavigationId) {
// A newer navigation has started, ignore this result
return;
}

// Check if this navigation was aborted
if (abortController.signal.aborted) {
return;
}

let currentPath = location.pathname;
let [newBasePath, newPathAnchor] = pathname.split('#');

Expand Down Expand Up @@ -59,9 +85,25 @@ async function navigate(pathname: string, push = false) {
});
});
} catch (error) {
// Check if this navigation was aborted
if (abortController.signal.aborted) {
return;
}

// Check if this navigation is still current
if (navigationId !== currentNavigationId) {
return;
}

clearPendingPage();
try {
let errorRes = await fetchRSC<ReactElement>('/error.rsc');

// Check again if still current after error fetch
if (navigationId !== currentNavigationId || abortController.signal.aborted) {
return;
}

await new Promise<void>((resolve) => {
updateRoot(errorRes, () => {
if (push) {
Expand All @@ -71,7 +113,10 @@ async function navigate(pathname: string, push = false) {
});
});
} catch {
ToastQueue.negative('Failed to load page. Check your connection and try again.');
// Only show error toast if this is still the current navigation
if (navigationId === currentNavigationId && !abortController.signal.aborted) {
ToastQueue.negative('Failed to load page. Check your connection and try again.');
}
throw error; // Re-throw to keep promise rejected
}
}
Expand Down