Skip to content

Commit f263433

Browse files
authored
Merge pull request #8097 from termermc/fix-spa-routing
fix: SPA routing is broken unless origin matches value in in vite.config #8093
2 parents 6cc1d22 + b1b63f1 commit f263433

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

.changeset/spicy-seas-check.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@builder.io/qwik-city': patch
3+
---
4+
5+
fix: SPA routing is broken unless origin matches value in in vite.config #8093
6+
7+
If the SSG origin was set to `localhost:3000` and a user visited from `127.0.0.1:3000`, SPA routing would be broken.
8+
9+
Internally, useNavigate's context provider `goto` checks the new destination with the last route location. If the
10+
origin is different, it just does a normal browser navigation. This makes sense; links to other origins cannot use
11+
SPA routing. However, the initial route it compares was using an origin that came from the server environment.
12+
13+
Now, the first navigation will set that initial route to the browser's actual href, eliminating the erroneous
14+
origin mismatch for SPA navigations.

packages/qwik-city/src/runtime/src/qwik-city-component.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,19 @@ export const QwikCityProvider = component$<QwikCityProps>((props) => {
139139
);
140140
const navResolver: { r?: () => void } = {};
141141
const loaderState = _weakSerialize(useStore(env.response.loaders, { deep: false }));
142+
143+
// The initial state of routeInternal uses the URL provided by the server environment.
144+
// It may not be accurate to the actual URL the browser is accessing the site from.
145+
// It is useful for the purposes of SSR and SSG, but may be overridden browser-side
146+
// if needed for SPA routing.
142147
const routeInternal = useSignal<RouteStateInternal>({
143148
type: 'initial',
144149
dest: url,
145150
forceReload: false,
146151
replaceState: false,
147152
scroll: true,
148153
});
154+
149155
const documentHead = useStore<Editable<ResolvedDocumentHead>>(createDocumentHead);
150156
const content = useStore<Editable<ContentState>>({
151157
headings: undefined,
@@ -218,6 +224,15 @@ export const QwikCityProvider = component$<QwikCityProps>((props) => {
218224
} = typeof opt === 'object' ? opt : { forceReload: opt };
219225
internalState.navCount++;
220226

227+
// If this is the first SPA navigation, we rewrite routeInternal's URL
228+
// as the browser location URL to prevent an erroneous origin mismatch.
229+
// The initial value of routeInternal is derived from the server env,
230+
// which in the case of SSG may not match the actual origin the site
231+
// is deployed on.
232+
if (isBrowser && routeInternal.value.type === 'initial') {
233+
routeInternal.value.dest = new URL(window.location.href);
234+
}
235+
221236
const lastDest = routeInternal.value.dest;
222237
const dest =
223238
path === undefined

0 commit comments

Comments
 (0)