@lolyjs/core 0.2.0-alpha.30 → 0.2.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -9870,6 +9870,8 @@ var src_exports = {};
9870
9870
  __export(src_exports, {
9871
9871
  DEFAULT_CONFIG: () => DEFAULT_CONFIG,
9872
9872
  Logger: () => Logger,
9873
+ NotFoundResponse: () => NotFoundResponse,
9874
+ RedirectResponse: () => RedirectResponse,
9873
9875
  ValidationError: () => ValidationError,
9874
9876
  bootstrapClient: () => bootstrapClient,
9875
9877
  buildApp: () => buildApp,
@@ -10815,6 +10817,18 @@ function writeRoutesManifest({
10815
10817
  import_fs7.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
10816
10818
  }
10817
10819
 
10820
+ // modules/router/index.types.ts
10821
+ var RedirectResponse = class {
10822
+ constructor(destination, permanent = false) {
10823
+ this.destination = destination;
10824
+ this.permanent = permanent;
10825
+ }
10826
+ };
10827
+ var NotFoundResponse = class {
10828
+ constructor() {
10829
+ }
10830
+ };
10831
+
10818
10832
  // modules/router/loader-routes.ts
10819
10833
  var import_fs8 = __toESM(require("fs"));
10820
10834
  var import_path10 = __toESM(require("path"));
@@ -15638,16 +15652,6 @@ async function runRouteServerHook(route, ctx) {
15638
15652
  // modules/server/handlers/response.ts
15639
15653
  function handleDataResponse(res, loaderResult, theme, layoutProps, pageProps, error, message) {
15640
15654
  res.setHeader("Content-Type", "application/json; charset=utf-8");
15641
- if (loaderResult.redirect) {
15642
- res.statusCode = 200;
15643
- res.end(JSON.stringify({ redirect: loaderResult.redirect }));
15644
- return;
15645
- }
15646
- if (loaderResult.notFound) {
15647
- res.statusCode = 404;
15648
- res.end(JSON.stringify({ notFound: true }));
15649
- return;
15650
- }
15651
15655
  const response = {
15652
15656
  // Combined props for backward compatibility
15653
15657
  props: loaderResult.props ?? {},
@@ -15768,6 +15772,145 @@ function mergeMetadata(base, override) {
15768
15772
  function isDataRequest(req) {
15769
15773
  return req.query && req.query.__fw_data === "1" || req.headers["x-fw-data"] === "1";
15770
15774
  }
15775
+ async function renderNotFoundPage(notFoundPage, req, res, urlPath, finalUrlPath, routerData, assetManifest, theme, clientJsPath, clientCssPath, faviconInfo, projectRoot, skipLayoutHooks, errorPage, routeChunks, env, config) {
15776
+ const ctx = {
15777
+ req,
15778
+ res,
15779
+ params: {},
15780
+ pathname: urlPath,
15781
+ locals: {},
15782
+ Redirect: (destination, permanent = false) => new RedirectResponse(destination, permanent),
15783
+ NotFound: () => new NotFoundResponse()
15784
+ };
15785
+ const layoutProps = {};
15786
+ if (!skipLayoutHooks && notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
15787
+ for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
15788
+ const layoutServerHook = notFoundPage.layoutServerHooks[i];
15789
+ const layoutMiddlewares = notFoundPage.layoutMiddlewares?.[i] || [];
15790
+ if (layoutMiddlewares.length > 0) {
15791
+ for (const mw of layoutMiddlewares) {
15792
+ try {
15793
+ await Promise.resolve(
15794
+ mw(ctx, async () => {
15795
+ })
15796
+ );
15797
+ } catch (error) {
15798
+ const reqLogger = getRequestLogger(req);
15799
+ const layoutFile = notFoundPage.layoutFiles[i];
15800
+ const relativeLayoutPath = layoutFile ? import_path25.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
15801
+ reqLogger.error("Layout middleware failed for not-found page", error instanceof Error ? error : new Error(String(error)), {
15802
+ layoutIndex: i,
15803
+ layoutFile: relativeLayoutPath
15804
+ });
15805
+ throw error;
15806
+ }
15807
+ if (ctx.res.headersSent) {
15808
+ return;
15809
+ }
15810
+ }
15811
+ }
15812
+ if (layoutServerHook) {
15813
+ try {
15814
+ const layoutResult = await layoutServerHook(ctx);
15815
+ if (layoutResult instanceof RedirectResponse) {
15816
+ handleRedirect(res, { destination: layoutResult.destination, permanent: layoutResult.permanent });
15817
+ return;
15818
+ }
15819
+ if (layoutResult instanceof NotFoundResponse) {
15820
+ handleNotFound(res, urlPath);
15821
+ return;
15822
+ }
15823
+ if (layoutResult.props) {
15824
+ Object.assign(layoutProps, layoutResult.props);
15825
+ }
15826
+ } catch (error) {
15827
+ const reqLogger = getRequestLogger(req);
15828
+ const layoutFile = notFoundPage.layoutFiles[i];
15829
+ const relativeLayoutPath = layoutFile ? import_path25.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
15830
+ reqLogger.warn("Layout server hook failed for not-found page", {
15831
+ error: error instanceof Error ? error.message : String(error),
15832
+ stack: error instanceof Error ? error.stack : void 0,
15833
+ layoutFile: relativeLayoutPath,
15834
+ layoutIndex: i
15835
+ });
15836
+ }
15837
+ }
15838
+ }
15839
+ }
15840
+ let loaderResult = await runRouteServerHook(notFoundPage, ctx);
15841
+ if (loaderResult instanceof RedirectResponse) {
15842
+ handleRedirect(res, { destination: loaderResult.destination, permanent: loaderResult.permanent });
15843
+ return;
15844
+ }
15845
+ if (loaderResult instanceof NotFoundResponse) {
15846
+ handleNotFound(res, urlPath);
15847
+ return;
15848
+ }
15849
+ const notFoundLoaderResult = loaderResult;
15850
+ if (!notFoundLoaderResult.theme) {
15851
+ notFoundLoaderResult.theme = theme;
15852
+ }
15853
+ const combinedProps = {
15854
+ ...layoutProps,
15855
+ ...notFoundLoaderResult.props || {}
15856
+ };
15857
+ const combinedLoaderResult = {
15858
+ ...notFoundLoaderResult,
15859
+ props: combinedProps
15860
+ };
15861
+ const initialData = buildInitialData(finalUrlPath, {}, combinedLoaderResult);
15862
+ const appTree = buildAppTree(notFoundPage, {}, initialData.props);
15863
+ initialData.notFound = true;
15864
+ const nonce = res.locals.nonce || void 0;
15865
+ const entrypointFiles = [];
15866
+ if (assetManifest?.entrypoints?.client) {
15867
+ entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
15868
+ }
15869
+ const documentTree = createDocumentTree({
15870
+ appTree,
15871
+ initialData,
15872
+ routerData,
15873
+ meta: combinedLoaderResult.metadata ?? null,
15874
+ titleFallback: "Not found",
15875
+ descriptionFallback: "Loly demo",
15876
+ chunkHref: null,
15877
+ entrypointFiles,
15878
+ theme,
15879
+ clientJsPath,
15880
+ clientCssPath,
15881
+ nonce,
15882
+ faviconPath: faviconInfo?.path || null,
15883
+ faviconType: faviconInfo?.type || null
15884
+ });
15885
+ let didError = false;
15886
+ const { pipe, abort } = (0, import_server.renderToPipeableStream)(documentTree, {
15887
+ onShellReady() {
15888
+ if (didError || res.headersSent) return;
15889
+ res.statusCode = 404;
15890
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
15891
+ pipe(res);
15892
+ },
15893
+ onShellError(err) {
15894
+ didError = true;
15895
+ const reqLogger = getRequestLogger(req);
15896
+ reqLogger.error("SSR shell error", err, { route: "not-found" });
15897
+ if (!res.headersSent && errorPage) {
15898
+ renderErrorPageWithStream(errorPage, req, res, err, routeChunks || {}, theme, projectRoot, env, config, notFoundPage);
15899
+ } else if (!res.headersSent) {
15900
+ res.statusCode = 500;
15901
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
15902
+ res.end("<!doctype html><h1>Internal Server Error</h1>");
15903
+ }
15904
+ abort();
15905
+ },
15906
+ onError(err) {
15907
+ didError = true;
15908
+ const reqLogger = getRequestLogger(req);
15909
+ reqLogger.error("SSR error", err, { route: "not-found" });
15910
+ }
15911
+ });
15912
+ req.on("close", () => abort());
15913
+ }
15771
15914
  async function handlePageRequest(options) {
15772
15915
  try {
15773
15916
  await handlePageRequestInternal(options);
@@ -15775,7 +15918,7 @@ async function handlePageRequest(options) {
15775
15918
  const { errorPage, req, res, routeChunks, theme, projectRoot } = options;
15776
15919
  const reqLogger = getRequestLogger(req);
15777
15920
  if (errorPage) {
15778
- await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot, options.env, options.config);
15921
+ await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot, options.env, options.config, options.notFoundPage);
15779
15922
  } else {
15780
15923
  reqLogger.error("Unhandled error in page request", error, {
15781
15924
  urlPath: options.urlPath,
@@ -15885,135 +16028,25 @@ async function handlePageRequestInternal(options) {
15885
16028
  return;
15886
16029
  }
15887
16030
  if (notFoundPage) {
15888
- const ctx2 = {
16031
+ await renderNotFoundPage(
16032
+ notFoundPage,
15889
16033
  req,
15890
16034
  res,
15891
- params: {},
15892
- pathname: urlPath,
15893
- locals: {}
15894
- };
15895
- const layoutProps2 = {};
15896
- if (!skipLayoutHooks && notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
15897
- for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
15898
- const layoutServerHook = notFoundPage.layoutServerHooks[i];
15899
- const layoutMiddlewares = notFoundPage.layoutMiddlewares?.[i] || [];
15900
- if (layoutMiddlewares.length > 0) {
15901
- for (const mw of layoutMiddlewares) {
15902
- try {
15903
- await Promise.resolve(
15904
- mw(ctx2, async () => {
15905
- })
15906
- );
15907
- } catch (error) {
15908
- const reqLogger2 = getRequestLogger(req);
15909
- const layoutFile = notFoundPage.layoutFiles[i];
15910
- const relativeLayoutPath = layoutFile ? import_path25.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
15911
- reqLogger2.error("Layout middleware failed for not-found page", error instanceof Error ? error : new Error(String(error)), {
15912
- layoutIndex: i,
15913
- layoutFile: relativeLayoutPath
15914
- });
15915
- throw error;
15916
- }
15917
- if (ctx2.res.headersSent) {
15918
- return;
15919
- }
15920
- }
15921
- }
15922
- if (layoutServerHook) {
15923
- try {
15924
- const layoutResult = await layoutServerHook(ctx2);
15925
- if (layoutResult.props) {
15926
- Object.assign(layoutProps2, layoutResult.props);
15927
- }
15928
- } catch (error) {
15929
- const reqLogger2 = getRequestLogger(req);
15930
- const layoutFile = notFoundPage.layoutFiles[i];
15931
- const relativeLayoutPath = layoutFile ? import_path25.default.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
15932
- reqLogger2.warn("Layout server hook failed for not-found page", {
15933
- error: error instanceof Error ? error.message : String(error),
15934
- stack: error instanceof Error ? error.stack : void 0,
15935
- layoutFile: relativeLayoutPath,
15936
- layoutIndex: i
15937
- });
15938
- }
15939
- }
15940
- }
15941
- }
15942
- let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
15943
- if (!loaderResult2.theme) {
15944
- loaderResult2.theme = theme;
15945
- }
15946
- const combinedProps2 = {
15947
- ...layoutProps2,
15948
- ...loaderResult2.props || {}
15949
- };
15950
- const combinedLoaderResult2 = {
15951
- ...loaderResult2,
15952
- props: combinedProps2
15953
- };
15954
- if (isDataReq) {
15955
- const pagePropsOnly = loaderResult2.props || {};
15956
- handleDataResponse(
15957
- res,
15958
- combinedLoaderResult2,
15959
- theme,
15960
- skipLayoutHooks ? null : Object.keys(layoutProps2).length > 0 ? layoutProps2 : null,
15961
- pagePropsOnly
15962
- );
15963
- return;
15964
- }
15965
- const initialData2 = buildInitialData(finalUrlPath, {}, combinedLoaderResult2);
15966
- const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
15967
- initialData2.notFound = true;
15968
- const nonce2 = res.locals.nonce || void 0;
15969
- const entrypointFiles2 = [];
15970
- if (assetManifest?.entrypoints?.client) {
15971
- entrypointFiles2.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
15972
- }
15973
- const documentTree2 = createDocumentTree({
15974
- appTree: appTree2,
15975
- initialData: initialData2,
16035
+ urlPath,
16036
+ finalUrlPath,
15976
16037
  routerData,
15977
- meta: combinedLoaderResult2.metadata ?? null,
15978
- titleFallback: "Not found",
15979
- descriptionFallback: "Loly demo",
15980
- chunkHref: null,
15981
- entrypointFiles: entrypointFiles2,
16038
+ assetManifest,
15982
16039
  theme,
15983
16040
  clientJsPath,
15984
16041
  clientCssPath,
15985
- nonce: nonce2,
15986
- faviconPath: faviconInfo?.path || null,
15987
- faviconType: faviconInfo?.type || null
15988
- });
15989
- let didError2 = false;
15990
- const { pipe: pipe2, abort: abort2 } = (0, import_server.renderToPipeableStream)(documentTree2, {
15991
- onShellReady() {
15992
- if (didError2 || res.headersSent) return;
15993
- res.statusCode = 404;
15994
- res.setHeader("Content-Type", "text/html; charset=utf-8");
15995
- pipe2(res);
15996
- },
15997
- onShellError(err) {
15998
- didError2 = true;
15999
- const reqLogger2 = getRequestLogger(req);
16000
- reqLogger2.error("SSR shell error", err, { route: "not-found" });
16001
- if (!res.headersSent && errorPage) {
16002
- renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env, config);
16003
- } else if (!res.headersSent) {
16004
- res.statusCode = 500;
16005
- res.setHeader("Content-Type", "text/html; charset=utf-8");
16006
- res.end("<!doctype html><h1>Internal Server Error</h1>");
16007
- }
16008
- abort2();
16009
- },
16010
- onError(err) {
16011
- didError2 = true;
16012
- const reqLogger2 = getRequestLogger(req);
16013
- reqLogger2.error("SSR error", err, { route: "not-found" });
16014
- }
16015
- });
16016
- req.on("close", () => abort2());
16042
+ faviconInfo,
16043
+ projectRoot,
16044
+ skipLayoutHooks,
16045
+ errorPage,
16046
+ routeChunks,
16047
+ env,
16048
+ config
16049
+ );
16017
16050
  return;
16018
16051
  }
16019
16052
  handleNotFound(res, urlPath);
@@ -16026,7 +16059,9 @@ async function handlePageRequestInternal(options) {
16026
16059
  res,
16027
16060
  params: sanitizedParams,
16028
16061
  pathname: urlPath,
16029
- locals: {}
16062
+ locals: {},
16063
+ Redirect: (destination, permanent = false) => new RedirectResponse(destination, permanent),
16064
+ NotFound: () => new NotFoundResponse()
16030
16065
  };
16031
16066
  await runRouteMiddlewares(route, ctx);
16032
16067
  if (res.headersSent) {
@@ -16064,6 +16099,48 @@ async function handlePageRequestInternal(options) {
16064
16099
  if (layoutServerHook) {
16065
16100
  try {
16066
16101
  const layoutResult = await layoutServerHook(ctx);
16102
+ if (layoutResult instanceof RedirectResponse) {
16103
+ if (isDataReq) {
16104
+ res.statusCode = 200;
16105
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16106
+ res.end(JSON.stringify({ redirect: { destination: layoutResult.destination, permanent: layoutResult.permanent } }));
16107
+ } else {
16108
+ handleRedirect(res, { destination: layoutResult.destination, permanent: layoutResult.permanent });
16109
+ }
16110
+ return;
16111
+ }
16112
+ if (layoutResult instanceof NotFoundResponse) {
16113
+ if (isDataReq) {
16114
+ res.statusCode = 200;
16115
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16116
+ res.end(JSON.stringify({ notFound: true }));
16117
+ } else {
16118
+ if (notFoundPage) {
16119
+ await renderNotFoundPage(
16120
+ notFoundPage,
16121
+ req,
16122
+ res,
16123
+ urlPath,
16124
+ finalUrlPath,
16125
+ routerData,
16126
+ assetManifest,
16127
+ theme,
16128
+ clientJsPath,
16129
+ clientCssPath,
16130
+ faviconInfo,
16131
+ projectRoot,
16132
+ skipLayoutHooks,
16133
+ errorPage,
16134
+ routeChunks,
16135
+ env,
16136
+ config
16137
+ );
16138
+ } else {
16139
+ handleNotFound(res, urlPath);
16140
+ }
16141
+ }
16142
+ return;
16143
+ }
16067
16144
  if (layoutResult.props) {
16068
16145
  Object.assign(layoutProps, layoutResult.props);
16069
16146
  }
@@ -16088,8 +16165,51 @@ async function handlePageRequestInternal(options) {
16088
16165
  let loaderResult;
16089
16166
  try {
16090
16167
  loaderResult = await runRouteServerHook(route, ctx);
16091
- if (!loaderResult.theme) {
16092
- loaderResult.theme = theme;
16168
+ if (loaderResult instanceof RedirectResponse) {
16169
+ if (isDataReq) {
16170
+ res.statusCode = 200;
16171
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16172
+ res.end(JSON.stringify({ redirect: { destination: loaderResult.destination, permanent: loaderResult.permanent } }));
16173
+ } else {
16174
+ handleRedirect(res, { destination: loaderResult.destination, permanent: loaderResult.permanent });
16175
+ }
16176
+ return;
16177
+ }
16178
+ if (loaderResult instanceof NotFoundResponse) {
16179
+ if (isDataReq) {
16180
+ res.statusCode = 200;
16181
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16182
+ res.end(JSON.stringify({ notFound: true }));
16183
+ } else {
16184
+ if (notFoundPage) {
16185
+ await renderNotFoundPage(
16186
+ notFoundPage,
16187
+ req,
16188
+ res,
16189
+ urlPath,
16190
+ finalUrlPath,
16191
+ routerData,
16192
+ assetManifest,
16193
+ theme,
16194
+ clientJsPath,
16195
+ clientCssPath,
16196
+ faviconInfo,
16197
+ projectRoot,
16198
+ skipLayoutHooks,
16199
+ errorPage,
16200
+ routeChunks,
16201
+ env,
16202
+ config
16203
+ );
16204
+ } else {
16205
+ handleNotFound(res, urlPath);
16206
+ }
16207
+ }
16208
+ return;
16209
+ }
16210
+ const pageLoaderResult2 = loaderResult;
16211
+ if (!pageLoaderResult2.theme) {
16212
+ pageLoaderResult2.theme = theme;
16093
16213
  }
16094
16214
  } catch (error) {
16095
16215
  const relativePagePath = route.pageFile ? import_path25.default.relative(projectRoot || process.cwd(), route.pageFile) : "unknown";
@@ -16114,17 +16234,18 @@ async function handlePageRequestInternal(options) {
16114
16234
  return;
16115
16235
  } else {
16116
16236
  if (errorPage) {
16117
- await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env, config);
16237
+ await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env, config, notFoundPage);
16118
16238
  return;
16119
16239
  } else {
16120
16240
  throw error;
16121
16241
  }
16122
16242
  }
16123
16243
  }
16244
+ const pageLoaderResult = loaderResult;
16124
16245
  const combinedProps = {
16125
16246
  ...layoutProps,
16126
16247
  // Props from layouts (stable)
16127
- ...loaderResult.props || {}
16248
+ ...pageLoaderResult.props || {}
16128
16249
  // Props from page (overrides layout)
16129
16250
  };
16130
16251
  let combinedMetadata = null;
@@ -16133,18 +16254,18 @@ async function handlePageRequestInternal(options) {
16133
16254
  combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
16134
16255
  }
16135
16256
  }
16136
- if (loaderResult.metadata) {
16137
- combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
16257
+ if (pageLoaderResult.metadata) {
16258
+ combinedMetadata = mergeMetadata(combinedMetadata, pageLoaderResult.metadata);
16138
16259
  }
16139
16260
  const combinedLoaderResult = {
16140
- ...loaderResult,
16261
+ ...pageLoaderResult,
16141
16262
  props: combinedProps,
16142
16263
  metadata: combinedMetadata,
16143
16264
  pathname: finalUrlPath
16144
16265
  // Include rewritten pathname for client-side matching
16145
16266
  };
16146
16267
  if (isDataReq) {
16147
- const pagePropsOnly = loaderResult.props || {};
16268
+ const pagePropsOnly = pageLoaderResult.props || {};
16148
16269
  handleDataResponse(
16149
16270
  res,
16150
16271
  combinedLoaderResult,
@@ -16154,18 +16275,6 @@ async function handlePageRequestInternal(options) {
16154
16275
  );
16155
16276
  return;
16156
16277
  }
16157
- if (loaderResult.redirect) {
16158
- handleRedirect(res, loaderResult.redirect);
16159
- return;
16160
- }
16161
- if (loaderResult.notFound) {
16162
- if (isDataReq) {
16163
- res.status(200).json({ notFound: true });
16164
- } else {
16165
- handleNotFound(res, urlPath);
16166
- }
16167
- return;
16168
- }
16169
16278
  const initialData = buildInitialData(finalUrlPath, params, combinedLoaderResult);
16170
16279
  const appTree = buildAppTree(route, params, initialData.props);
16171
16280
  const chunkName = routeChunks[route.pattern];
@@ -16222,7 +16331,7 @@ async function handlePageRequestInternal(options) {
16222
16331
  }
16223
16332
  console.error("\u{1F4A1} This usually indicates a React rendering error\n");
16224
16333
  if (!res.headersSent && errorPage) {
16225
- renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
16334
+ renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env, config, notFoundPage);
16226
16335
  } else if (!res.headersSent) {
16227
16336
  res.statusCode = 500;
16228
16337
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -16244,7 +16353,7 @@ async function handlePageRequestInternal(options) {
16244
16353
  abort();
16245
16354
  });
16246
16355
  }
16247
- async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev", config) {
16356
+ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev", config, notFoundPage) {
16248
16357
  try {
16249
16358
  const isDataReq = isDataRequest(req);
16250
16359
  const skipLayoutHooks = isDataReq && req.headers["x-skip-layout-hooks"] === "true";
@@ -16253,7 +16362,9 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
16253
16362
  res,
16254
16363
  params: { error: String(error) },
16255
16364
  pathname: req.path,
16256
- locals: { error }
16365
+ locals: { error },
16366
+ Redirect: (destination, permanent = false) => new RedirectResponse(destination, permanent),
16367
+ NotFound: () => new NotFoundResponse()
16257
16368
  };
16258
16369
  const faviconInfo = projectRoot && config ? getFaviconInfo(projectRoot, config.directories.static, env === "dev") : null;
16259
16370
  const layoutProps = {};
@@ -16286,6 +16397,26 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
16286
16397
  if (layoutServerHook) {
16287
16398
  try {
16288
16399
  const layoutResult = await layoutServerHook(ctx);
16400
+ if (layoutResult instanceof RedirectResponse) {
16401
+ if (isDataReq) {
16402
+ res.statusCode = 200;
16403
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16404
+ res.end(JSON.stringify({ redirect: { destination: layoutResult.destination, permanent: layoutResult.permanent } }));
16405
+ } else {
16406
+ handleRedirect(res, { destination: layoutResult.destination, permanent: layoutResult.permanent });
16407
+ }
16408
+ return;
16409
+ }
16410
+ if (layoutResult instanceof NotFoundResponse) {
16411
+ if (isDataReq) {
16412
+ res.statusCode = 200;
16413
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16414
+ res.end(JSON.stringify({ notFound: true }));
16415
+ } else {
16416
+ handleNotFound(res, req.path);
16417
+ }
16418
+ return;
16419
+ }
16289
16420
  if (layoutResult.props) {
16290
16421
  Object.assign(layoutProps, layoutResult.props);
16291
16422
  }
@@ -16303,22 +16434,69 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
16303
16434
  }
16304
16435
  }
16305
16436
  let loaderResult = await runRouteServerHook(errorPage, ctx);
16306
- if (!loaderResult.theme && theme) {
16307
- loaderResult.theme = theme;
16437
+ if (loaderResult instanceof RedirectResponse) {
16438
+ if (isDataReq) {
16439
+ res.statusCode = 200;
16440
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16441
+ res.end(JSON.stringify({ redirect: { destination: loaderResult.destination, permanent: loaderResult.permanent } }));
16442
+ } else {
16443
+ handleRedirect(res, { destination: loaderResult.destination, permanent: loaderResult.permanent });
16444
+ }
16445
+ return;
16446
+ }
16447
+ if (loaderResult instanceof NotFoundResponse) {
16448
+ if (isDataReq) {
16449
+ res.statusCode = 200;
16450
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16451
+ res.end(JSON.stringify({ notFound: true }));
16452
+ } else {
16453
+ if (notFoundPage) {
16454
+ const notFoundAssetManifest = env === "prod" && projectRoot ? loadAssetManifest(projectRoot) : null;
16455
+ const notFoundClientJsPath = env === "dev" ? "/static/client.js" : projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
16456
+ const notFoundClientCssPath = env === "dev" ? "/static/client.css" : projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
16457
+ const notFoundFaviconInfo = projectRoot && config ? getFaviconInfo(projectRoot, config.directories.static, env === "dev") : null;
16458
+ await renderNotFoundPage(
16459
+ notFoundPage,
16460
+ req,
16461
+ res,
16462
+ req.path,
16463
+ req.path,
16464
+ buildRouterData(req),
16465
+ notFoundAssetManifest,
16466
+ theme,
16467
+ notFoundClientJsPath,
16468
+ notFoundClientCssPath,
16469
+ notFoundFaviconInfo,
16470
+ projectRoot,
16471
+ false,
16472
+ errorPage,
16473
+ routeChunks,
16474
+ env,
16475
+ config
16476
+ );
16477
+ } else {
16478
+ handleNotFound(res, req.path);
16479
+ }
16480
+ }
16481
+ return;
16482
+ }
16483
+ const errorLoaderResult = loaderResult;
16484
+ if (!errorLoaderResult.theme && theme) {
16485
+ errorLoaderResult.theme = theme;
16308
16486
  }
16309
16487
  const combinedProps = {
16310
16488
  ...layoutProps,
16311
- ...loaderResult.props || {}
16489
+ ...errorLoaderResult.props || {}
16312
16490
  };
16313
16491
  const combinedLoaderResult = {
16314
- ...loaderResult,
16492
+ ...errorLoaderResult,
16315
16493
  props: combinedProps
16316
16494
  };
16317
16495
  const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
16318
16496
  const routerData = buildRouterData(req);
16319
16497
  initialData.error = true;
16320
16498
  if (isDataReq) {
16321
- const pagePropsOnly = loaderResult.props || {};
16499
+ const pagePropsOnly = errorLoaderResult.props || {};
16322
16500
  handleDataResponse(
16323
16501
  res,
16324
16502
  combinedLoaderResult,
@@ -17999,7 +18177,9 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params,
17999
18177
  res,
18000
18178
  params,
18001
18179
  pathname: urlPath,
18002
- locals: {}
18180
+ locals: {},
18181
+ Redirect: (destination, permanent = false) => new RedirectResponse(destination, permanent),
18182
+ NotFound: () => new NotFoundResponse()
18003
18183
  };
18004
18184
  for (const mw of route.middlewares) {
18005
18185
  await Promise.resolve(
@@ -18015,6 +18195,12 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params,
18015
18195
  if (layoutServerHook) {
18016
18196
  try {
18017
18197
  const layoutResult = await layoutServerHook(ctx);
18198
+ if (layoutResult instanceof RedirectResponse || layoutResult instanceof NotFoundResponse) {
18199
+ console.warn(
18200
+ `\u26A0\uFE0F [framework][ssg] Layout server hook ${i} returned redirect/notFound for route ${route.pattern}. Redirect/NotFound is not supported in SSG (Static Site Generation). Skipping this route.`
18201
+ );
18202
+ return;
18203
+ }
18018
18204
  if (layoutResult.props) {
18019
18205
  Object.assign(layoutProps, layoutResult.props);
18020
18206
  }
@@ -18036,10 +18222,17 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params,
18036
18222
  let loaderResult = { props: {} };
18037
18223
  if (route.loader) {
18038
18224
  loaderResult = await route.loader(ctx);
18225
+ if (loaderResult instanceof RedirectResponse || loaderResult instanceof NotFoundResponse) {
18226
+ console.warn(
18227
+ `\u26A0\uFE0F [framework][ssg] Page server hook returned redirect/notFound for route ${route.pattern} (${urlPath}). Redirect/NotFound is not supported in SSG (Static Site Generation). Skipping this route.`
18228
+ );
18229
+ return;
18230
+ }
18039
18231
  }
18232
+ const pageLoaderResult = loaderResult;
18040
18233
  const combinedProps = {
18041
18234
  ...layoutProps,
18042
- ...loaderResult.props || {}
18235
+ ...pageLoaderResult.props || {}
18043
18236
  };
18044
18237
  let combinedMetadata = null;
18045
18238
  for (const layoutMeta of layoutMetadata) {
@@ -18047,17 +18240,14 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params,
18047
18240
  combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
18048
18241
  }
18049
18242
  }
18050
- if (loaderResult.metadata) {
18051
- combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
18243
+ if (pageLoaderResult.metadata) {
18244
+ combinedMetadata = mergeMetadata(combinedMetadata, pageLoaderResult.metadata);
18052
18245
  }
18053
18246
  const combinedLoaderResult = {
18054
- ...loaderResult,
18247
+ ...pageLoaderResult,
18055
18248
  props: combinedProps,
18056
18249
  metadata: combinedMetadata
18057
18250
  };
18058
- if (combinedLoaderResult.redirect || combinedLoaderResult.notFound) {
18059
- return;
18060
- }
18061
18251
  const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
18062
18252
  const routerData = buildRouterData(req);
18063
18253
  const appTree = buildAppTree(route, params, initialData.props);
@@ -19659,6 +19849,8 @@ var commonSchemas = {
19659
19849
  0 && (module.exports = {
19660
19850
  DEFAULT_CONFIG,
19661
19851
  Logger,
19852
+ NotFoundResponse,
19853
+ RedirectResponse,
19662
19854
  ValidationError,
19663
19855
  bootstrapClient,
19664
19856
  buildApp,