1
1
import { onMount , tick } from 'svelte' ;
2
- import { normalize_error } from '../../utils/error.js' ;
3
2
import { make_trackable , decode_params , normalize_path } from '../../utils/url.js' ;
4
3
import { find_anchor , get_base_uri , scroll_state } from './utils.js' ;
5
4
import { lock_fetch , unlock_fetch , initial_fetch , subsequent_fetch } from './fetcher.js' ;
@@ -92,6 +91,8 @@ export function create_client({ target, base, trailing_slash }) {
92
91
url : null
93
92
} ;
94
93
94
+ /** this being true means we SSR'd */
95
+ let hydrated = false ;
95
96
let started = false ;
96
97
let autoscroll = true ;
97
98
let updating = false ;
@@ -211,27 +212,13 @@ export function create_client({ target, base, trailing_slash }) {
211
212
token = nav_token ;
212
213
let navigation_result = intent && ( await load_route ( intent ) ) ;
213
214
214
- if (
215
- ! navigation_result &&
216
- url . origin === location . origin &&
217
- url . pathname === location . pathname
218
- ) {
219
- // this could happen in SPA fallback mode if the user navigated to
220
- // `/non-existent-page`. if we fall back to reloading the page, it
221
- // will create an infinite loop. so whereas we normally handle
222
- // unknown routes by going to the server, in this special case
223
- // we render a client-side error page instead
224
- navigation_result = await load_root_error_page ( {
225
- status : 404 ,
226
- error : new Error ( `Not found: ${ url . pathname } ` ) ,
227
- url,
228
- routeId : null
229
- } ) ;
230
- }
231
-
232
215
if ( ! navigation_result ) {
233
- await native_navigation ( url ) ;
234
- return false ; // unnecessary, but TypeScript prefers it this way
216
+ navigation_result = await server_fallback (
217
+ url ,
218
+ null ,
219
+ handle_error ( new Error ( `Not found: ${ url . pathname } ` ) , { url, params : { } , routeId : null } ) ,
220
+ 404
221
+ ) ;
235
222
}
236
223
237
224
// if this is an internal navigation intent, use the normalized
@@ -245,7 +232,7 @@ export function create_client({ target, base, trailing_slash }) {
245
232
if ( redirect_chain . length > 10 || redirect_chain . includes ( url . pathname ) ) {
246
233
navigation_result = await load_root_error_page ( {
247
234
status : 500 ,
248
- error : new Error ( 'Redirect loop' ) ,
235
+ error : handle_error ( new Error ( 'Redirect loop' ) , { url , params : { } , routeId : null } ) ,
249
236
url,
250
237
routeId : null
251
238
} ) ;
@@ -722,7 +709,7 @@ export function create_client({ target, base, trailing_slash }) {
722
709
} catch ( error ) {
723
710
return load_root_error_page ( {
724
711
status : 500 ,
725
- error : /** @type { Error } */ ( error ) ,
712
+ error : handle_error ( error , { url , params , routeId : route . id } ) ,
726
713
url,
727
714
routeId : route . id
728
715
} ) ;
@@ -827,8 +814,7 @@ export function create_client({ target, base, trailing_slash }) {
827
814
} else {
828
815
// if we get here, it's because the root `load` function failed,
829
816
// and we need to fall back to the server
830
- await native_navigation ( url ) ;
831
- return ;
817
+ return await server_fallback ( url , route . id , error , status ) ;
832
818
}
833
819
}
834
820
} else {
@@ -882,7 +868,7 @@ export function create_client({ target, base, trailing_slash }) {
882
868
/**
883
869
* @param {{
884
870
* status: number;
885
- * error: HttpError | Error;
871
+ * error: App. Error;
886
872
* url: URL;
887
873
* routeId: string | null
888
874
* }} opts
@@ -912,11 +898,11 @@ export function create_client({ target, base, trailing_slash }) {
912
898
913
899
server_data_node = server_data . nodes [ 0 ] ?? null ;
914
900
} catch {
915
- // at this point we have no choice but to fall back to the server
916
- await native_navigation ( url ) ;
917
-
918
- // @ts -expect-error
919
- return ;
901
+ // at this point we have no choice but to fall back to the server, if it wouldn't
902
+ // bring us right back here, turning this into an endless loop
903
+ if ( url . origin !== location . origin || url . pathname !== location . pathname || hydrated ) {
904
+ await native_navigation ( url ) ;
905
+ }
920
906
}
921
907
}
922
908
@@ -943,10 +929,7 @@ export function create_client({ target, base, trailing_slash }) {
943
929
params,
944
930
branch : [ root_layout , root_error ] ,
945
931
status,
946
- error :
947
- error instanceof HttpError
948
- ? error . body
949
- : handle_error ( error , { url, params, routeId : null } ) ,
932
+ error,
950
933
route : null
951
934
} ) ;
952
935
}
@@ -1071,6 +1054,28 @@ export function create_client({ target, base, trailing_slash }) {
1071
1054
) ;
1072
1055
}
1073
1056
1057
+ /**
1058
+ * Does a full page reload if it wouldn't result in an endless loop in the SPA case
1059
+ * @param {URL } url
1060
+ * @param {string | null } routeId
1061
+ * @param {App.Error } error
1062
+ * @param {number } status
1063
+ * @returns {Promise<import('./types').NavigationFinished> }
1064
+ */
1065
+ async function server_fallback ( url , routeId , error , status ) {
1066
+ if ( url . origin === location . origin && url . pathname === location . pathname && ! hydrated ) {
1067
+ // We would reload the same page we're currently on, which isn't hydrated,
1068
+ // which means no SSR, which means we would end up in an endless loop
1069
+ return await load_root_error_page ( {
1070
+ status,
1071
+ error,
1072
+ url,
1073
+ routeId
1074
+ } ) ;
1075
+ }
1076
+ return await native_navigation ( url ) ;
1077
+ }
1078
+
1074
1079
/**
1075
1080
* Loads `href` the old-fashioned way, with a full page reload.
1076
1081
* Returns a `Promise` that never resolves (to prevent any
@@ -1413,6 +1418,8 @@ export function create_client({ target, base, trailing_slash }) {
1413
1418
data : server_data_nodes ,
1414
1419
form
1415
1420
} ) => {
1421
+ hydrated = true ;
1422
+
1416
1423
const url = new URL ( location . href ) ;
1417
1424
1418
1425
/** @type {import('./types').NavigationFinished | undefined } */
@@ -1447,19 +1454,17 @@ export function create_client({ target, base, trailing_slash }) {
1447
1454
form,
1448
1455
route : routes . find ( ( route ) => route . id === routeId ) ?? null
1449
1456
} ) ;
1450
- } catch ( e ) {
1451
- const error = normalize_error ( e ) ;
1452
-
1457
+ } catch ( error ) {
1453
1458
if ( error instanceof Redirect ) {
1454
1459
// this is a real edge case — `load` would need to return
1455
1460
// a redirect but only in the browser
1456
- await native_navigation ( new URL ( /** @type { Redirect } */ ( e ) . location , location . href ) ) ;
1461
+ await native_navigation ( new URL ( error . location , location . href ) ) ;
1457
1462
return ;
1458
1463
}
1459
1464
1460
1465
result = await load_root_error_page ( {
1461
1466
status : error instanceof HttpError ? error . status : 500 ,
1462
- error,
1467
+ error : handle_error ( error , { url , params , routeId } ) ,
1463
1468
url,
1464
1469
routeId
1465
1470
} ) ;
@@ -1507,9 +1512,12 @@ async function load_data(url, invalid) {
1507
1512
* @returns {App.Error }
1508
1513
*/
1509
1514
function handle_error ( error , event ) {
1515
+ if ( error instanceof HttpError ) {
1516
+ return error . body ;
1517
+ }
1510
1518
return (
1511
1519
hooks . handleError ( { error, event } ) ??
1512
- /** @type {any } */ ( { message : event . routeId ? 'Internal Error' : 'Not Found' } )
1520
+ /** @type {any } */ ( { message : event . routeId != null ? 'Internal Error' : 'Not Found' } )
1513
1521
) ;
1514
1522
}
1515
1523
0 commit comments