@farm.js/core 0.1.0-beta.10 → 0.1.0-beta.13

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.
Files changed (79) hide show
  1. package/dist/{chunk-3QDB3IA6.mjs → chunk-3WIRO3N7.mjs} +2 -2
  2. package/dist/chunk-3WIRO3N7.mjs.map +1 -0
  3. package/dist/chunk-53MVSX3R.mjs +168 -0
  4. package/dist/chunk-53MVSX3R.mjs.map +1 -0
  5. package/dist/{chunk-AW6I64AP.mjs → chunk-6ZXIQSXE.mjs} +16 -1
  6. package/dist/chunk-6ZXIQSXE.mjs.map +1 -0
  7. package/dist/chunk-7ZVN3YZI.mjs +333 -0
  8. package/dist/chunk-7ZVN3YZI.mjs.map +1 -0
  9. package/dist/{chunk-7N62DNSO.mjs → chunk-H3TE3LOV.mjs} +2 -2
  10. package/dist/{chunk-QTZZ2ISQ.mjs → chunk-KLA67WKG.mjs} +1 -1
  11. package/dist/chunk-KLA67WKG.mjs.map +1 -0
  12. package/dist/{chunk-K5ZTUF22.mjs → chunk-L2PE36GQ.mjs} +7 -3
  13. package/dist/chunk-L2PE36GQ.mjs.map +1 -0
  14. package/dist/{chunk-4LND3PXF.mjs → chunk-OPHK6OJN.mjs} +513 -48
  15. package/dist/chunk-OPHK6OJN.mjs.map +1 -0
  16. package/dist/chunk-USLFXFFH.mjs +46 -0
  17. package/dist/chunk-USLFXFFH.mjs.map +1 -0
  18. package/dist/{chunk-DTOWEQRV.mjs → chunk-VSDZEB5M.mjs} +368 -103
  19. package/dist/chunk-VSDZEB5M.mjs.map +1 -0
  20. package/dist/{chunk-E2AI2CW7.mjs → chunk-Z3YG4OZT.mjs} +727 -148
  21. package/dist/chunk-Z3YG4OZT.mjs.map +1 -0
  22. package/dist/client.cjs.map +1 -1
  23. package/dist/client.mjs +1 -1
  24. package/dist/{devtools-A22KUJZT.mjs → devtools-GAZ7F3KL.mjs} +4 -3
  25. package/dist/{devtools-A22KUJZT.mjs.map → devtools-GAZ7F3KL.mjs.map} +1 -1
  26. package/dist/docs.cjs +622 -124
  27. package/dist/docs.cjs.map +1 -1
  28. package/dist/docs.mjs +5 -3
  29. package/dist/font.cjs +19 -2
  30. package/dist/font.cjs.map +1 -1
  31. package/dist/font.mjs +7 -3
  32. package/dist/index.cjs +2208 -681
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.mjs +27 -19
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/internal/build-runtime.cjs +2035 -517
  37. package/dist/internal/build-runtime.cjs.map +1 -1
  38. package/dist/internal/build-runtime.mjs +13 -10
  39. package/dist/internal/client-runtime.cjs +169 -2
  40. package/dist/internal/client-runtime.cjs.map +1 -1
  41. package/dist/internal/client-runtime.mjs +170 -2
  42. package/dist/internal/client-runtime.mjs.map +1 -1
  43. package/dist/internal/config-runtime.cjs +22 -0
  44. package/dist/internal/config-runtime.cjs.map +1 -1
  45. package/dist/internal/config-runtime.mjs +3 -2
  46. package/dist/internal/metadata-image-runtime.cjs +199 -0
  47. package/dist/internal/metadata-image-runtime.cjs.map +1 -0
  48. package/dist/internal/metadata-image-runtime.mjs +8 -0
  49. package/dist/internal/metadata-image-runtime.mjs.map +1 -0
  50. package/dist/internal/production-runtime.cjs +310 -0
  51. package/dist/internal/production-runtime.cjs.map +1 -1
  52. package/dist/internal/production-runtime.mjs +10 -0
  53. package/dist/internal/production-runtime.mjs.map +1 -1
  54. package/dist/navigation.cjs.map +1 -1
  55. package/dist/navigation.mjs +2 -2
  56. package/dist/plugin.cjs +1523 -340
  57. package/dist/plugin.cjs.map +1 -1
  58. package/dist/plugin.mjs +9 -5
  59. package/dist/server.cjs +1636 -411
  60. package/dist/server.cjs.map +1 -1
  61. package/dist/server.mjs +17 -19
  62. package/dist/server.mjs.map +1 -1
  63. package/dist/version.cjs +1 -1
  64. package/dist/version.cjs.map +1 -1
  65. package/dist/version.mjs +1 -1
  66. package/dist/vite.cjs +1577 -350
  67. package/dist/vite.cjs.map +1 -1
  68. package/dist/vite.mjs +9 -5
  69. package/package.json +10 -1
  70. package/dist/chunk-3QDB3IA6.mjs.map +0 -1
  71. package/dist/chunk-4LND3PXF.mjs.map +0 -1
  72. package/dist/chunk-AW6I64AP.mjs.map +0 -1
  73. package/dist/chunk-DTOWEQRV.mjs.map +0 -1
  74. package/dist/chunk-E2AI2CW7.mjs.map +0 -1
  75. package/dist/chunk-K5ZTUF22.mjs.map +0 -1
  76. package/dist/chunk-QM746KUO.mjs +0 -56
  77. package/dist/chunk-QM746KUO.mjs.map +0 -1
  78. package/dist/chunk-QTZZ2ISQ.mjs.map +0 -1
  79. /package/dist/{chunk-7N62DNSO.mjs.map → chunk-H3TE3LOV.mjs.map} +0 -0
@@ -1,27 +1,24 @@
1
+ import {
2
+ withProductionNodeEnv
3
+ } from "./chunk-4S3ORGFA.mjs";
1
4
  import {
2
5
  canUseRolldownForRouteDiscovery,
3
6
  loadFarmProductionVite
4
7
  } from "./chunk-SL6HTIOK.mjs";
5
- import {
6
- withProductionNodeEnv
7
- } from "./chunk-4S3ORGFA.mjs";
8
8
  import {
9
9
  FARM_DOCS_LAST_MODIFIED_MANIFEST,
10
10
  createFarmDocsLastModifiedManifest,
11
11
  getFarmDocsRouteTypeEntries
12
- } from "./chunk-4LND3PXF.mjs";
12
+ } from "./chunk-OPHK6OJN.mjs";
13
13
  import {
14
14
  generateFarmDocsSearchClientRuntime,
15
15
  isFarmDocsSearchEnabled,
16
16
  resolveFarmDocsSearchClientModule
17
17
  } from "./chunk-V7RBCSDJ.mjs";
18
- import {
19
- mergeFarmViteConfig
20
- } from "./chunk-QM746KUO.mjs";
21
18
  import {
22
19
  hasCustomFarmRouteContext,
23
20
  resolveDeployOutputPath
24
- } from "./chunk-K5ZTUF22.mjs";
21
+ } from "./chunk-L2PE36GQ.mjs";
25
22
  import {
26
23
  PluginManager
27
24
  } from "./chunk-DJSGLHCN.mjs";
@@ -32,11 +29,16 @@ import {
32
29
  generateFarmTypeArtifacts,
33
30
  getClientModuleMetadata,
34
31
  mergeFarmFontCss,
32
+ mergeFarmViteConfig,
35
33
  resolveRouteRenderingConfigFromFile
36
- } from "./chunk-E2AI2CW7.mjs";
34
+ } from "./chunk-Z3YG4OZT.mjs";
37
35
  import {
38
36
  farmEnvironmentFunctionsPlugin
39
37
  } from "./chunk-V6WE6JSZ.mjs";
38
+ import {
39
+ resolveFarmDocsFontAssets,
40
+ toFarmDocsPublicFontAssets
41
+ } from "./chunk-USLFXFFH.mjs";
40
42
  import {
41
43
  routeRulesToNitroRouteRules
42
44
  } from "./chunk-YW7OBE6Q.mjs";
@@ -319,6 +321,31 @@ function escapeRegExp(value) {
319
321
  }
320
322
  __name(escapeRegExp, "escapeRegExp");
321
323
 
324
+ // src/nitro/vercel-assets.ts
325
+ var FARM_IMMUTABLE_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
326
+ function createFarmVercelImmutableAssetRoute(basePath = "/") {
327
+ const normalizedBasePath = normalizeBasePath(basePath);
328
+ const escapedBasePath = escapeRegex(normalizedBasePath);
329
+ const prefix = escapedBasePath ? `/${escapedBasePath}` : "";
330
+ return {
331
+ src: `^${prefix}/(?:assets|chunks)/(?:.+/)*[^/]+-h(?:[a-fA-F0-9]{8}|[a-fA-F0-9]{12}|[a-fA-F0-9]{16})\\.(?!(?:[hH][tT][mM][lL]?)$)[^/]+$`,
332
+ headers: {
333
+ "Cache-Control": FARM_IMMUTABLE_ASSET_CACHE_CONTROL
334
+ },
335
+ continue: true,
336
+ caseSensitive: true
337
+ };
338
+ }
339
+ __name(createFarmVercelImmutableAssetRoute, "createFarmVercelImmutableAssetRoute");
340
+ function normalizeBasePath(basePath) {
341
+ return basePath.trim().replace(/^\/+|\/+$/g, "");
342
+ }
343
+ __name(normalizeBasePath, "normalizeBasePath");
344
+ function escapeRegex(value) {
345
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
346
+ }
347
+ __name(escapeRegex, "escapeRegex");
348
+
322
349
  // src/build/vite-plugin-compat.ts
323
350
  function adaptTailwindVitePlugin(pluginOption, builder) {
324
351
  if (builder !== "rolldown") return pluginOption;
@@ -463,6 +490,10 @@ function isCloudflareImagePreset(preset) {
463
490
  return preset === "cloudflare" || preset === "cloudflare-pages" || preset === "cloudflare-module";
464
491
  }
465
492
  __name(isCloudflareImagePreset, "isCloudflareImagePreset");
493
+ function shouldUseExternalMetadataImageRuntime(preset, hasGeneratedMetadataImages) {
494
+ return hasGeneratedMetadataImages && preset !== "vercel-edge" && !isCloudflareImagePreset(preset);
495
+ }
496
+ __name(shouldUseExternalMetadataImageRuntime, "shouldUseExternalMetadataImageRuntime");
466
497
  function resolveImageRuntime(config, preset) {
467
498
  if (config.images.provider === "none") return "none";
468
499
  const cloudflarePreset = isCloudflareImagePreset(preset);
@@ -480,6 +511,31 @@ function isNitroRollupExternal(id) {
480
511
  return NODE_BUILTIN_MODULES.has(id) || NITRO_EXTERNAL_MODULES.has(id) || normalizedId.startsWith(".prisma/") || normalizedId.includes("/node_modules/@prisma/client/") || normalizedId.includes("/node_modules/.prisma/client/");
481
512
  }
482
513
  __name(isNitroRollupExternal, "isNitroRollupExternal");
514
+ var FARM_METADATA_IMAGE_WASM_PREFIX = "\0farm-metadata-image-wasm:";
515
+ function createMetadataImageWasmPlugin() {
516
+ return {
517
+ name: "farm-metadata-image-wasm",
518
+ resolveId(source, importer) {
519
+ if (!source.endsWith(".wasm?module") || !importer?.replace(/\\/g, "/").includes("/@vercel/og/")) {
520
+ return null;
521
+ }
522
+ const requestPath = source.slice(0, -"?module".length);
523
+ const resolvedPath = path3.isAbsolute(requestPath) ? requestPath : path3.resolve(path3.dirname(importer.split("?", 1)[0]), requestPath);
524
+ return `${FARM_METADATA_IMAGE_WASM_PREFIX}${resolvedPath}`;
525
+ },
526
+ load(id) {
527
+ if (!id.startsWith(FARM_METADATA_IMAGE_WASM_PREFIX)) return null;
528
+ const wasmPath = id.slice(FARM_METADATA_IMAGE_WASM_PREFIX.length);
529
+ const encoded = readFileSync(wasmPath).toString("base64");
530
+ return `
531
+ const encoded = ${JSON.stringify(encoded)};
532
+ const bytes = Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0));
533
+ export default new WebAssembly.Module(bytes);
534
+ `;
535
+ }
536
+ };
537
+ }
538
+ __name(createMetadataImageWasmPlugin, "createMetadataImageWasmPlugin");
483
539
  function collectSSRExternalPackages(ssrBundle) {
484
540
  const emittedFiles = new Set(
485
541
  Object.keys(ssrBundle).map((fileName) => fileName.replace(/^\.\//, ""))
@@ -863,6 +919,9 @@ async function buildUniversal(config, routeManager, apiRouteManager, serverRende
863
919
  }
864
920
  const { bundle: ssrBundle, entryFile: ssrEntryFile, configuredHeaderRoutes } = ssrResult;
865
921
  await writeSSRAssetsToClient(ssrBundle, clientOutputDir);
922
+ if (config.docs.enabled) {
923
+ await copyFarmDocsFontAssetsToClient(root, clientOutputDir);
924
+ }
866
925
  logger.info(`\u{1F680} Building server with Nitro (preset: ${preset})...`);
867
926
  await buildNitroUniversal(
868
927
  config,
@@ -917,6 +976,17 @@ async function writeSSRAssetsToClient(bundle, outputDir) {
917
976
  }
918
977
  }
919
978
  __name(writeSSRAssetsToClient, "writeSSRAssetsToClient");
979
+ async function copyFarmDocsFontAssetsToClient(root, outputDir) {
980
+ const fs = await import("fs/promises");
981
+ await Promise.all(
982
+ resolveFarmDocsFontAssets(root).map(async ({ sourcePath, url }) => {
983
+ const targetPath = path3.join(outputDir, url.replace(/^\/+/, ""));
984
+ await fs.mkdir(path3.dirname(targetPath), { recursive: true });
985
+ await fs.copyFile(sourcePath, targetPath);
986
+ })
987
+ );
988
+ }
989
+ __name(copyFarmDocsFontAssetsToClient, "copyFarmDocsFontAssetsToClient");
920
990
  async function mergeBuiltFontCss(outputDir, fontCssSource) {
921
991
  const fs = await import("fs/promises");
922
992
  const fontCssPath = path3.join(outputDir, "farm-fonts.css");
@@ -935,11 +1005,8 @@ async function mergeBuiltFontCss(outputDir, fontCssSource) {
935
1005
  } catch {
936
1006
  }
937
1007
  const normalizedFontCss = typeof fontCss === "string" ? fontCss : Buffer.from(fontCss).toString("utf8");
1008
+ await fs.writeFile(fontCssPath, normalizedFontCss);
938
1009
  await fs.writeFile(clientCssPath, mergeFarmFontCss(clientCss, normalizedFontCss));
939
- try {
940
- await fs.rm(fontCssPath, { force: true });
941
- } catch {
942
- }
943
1010
  }
944
1011
  __name(mergeBuiltFontCss, "mergeBuiltFontCss");
945
1012
  var _IncompleteClientBuildOutputError = class _IncompleteClientBuildOutputError extends Error {
@@ -1004,7 +1071,11 @@ async function buildClient(productionVite, config, root, srcDir, outputDir, page
1004
1071
  const metadata = getClientModuleMetadata(route.modulePath, root);
1005
1072
  if (metadata.shouldHydrate) {
1006
1073
  const relativePath = route.modulePath.replace(root, "").replace(/^\//, "");
1007
- clientPages.push({ ...route, relativePath });
1074
+ clientPages.push({
1075
+ ...route,
1076
+ relativePath,
1077
+ islandStrategy: metadata.islandStrategy ?? "load"
1078
+ });
1008
1079
  logger.info(`\u{1F4F1} Found hydratable route: ${route.pattern} -> ${route.modulePath}`);
1009
1080
  }
1010
1081
  } catch (error) {
@@ -1078,13 +1149,14 @@ async function buildClient(productionVite, config, root, srcDir, outputDir, page
1078
1149
  },
1079
1150
  output: {
1080
1151
  entryFileNames: "[name].js",
1081
- chunkFileNames: "chunks/[name]-[hash].js",
1152
+ chunkFileNames: "chunks/[name]-h[hash].js",
1153
+ hashCharacters: "hex",
1082
1154
  // Use predictable name for CSS so we can reference it in SSR HTML
1083
1155
  assetFileNames: /* @__PURE__ */ __name((assetInfo) => {
1084
1156
  if (assetInfo.name?.endsWith(".css")) {
1085
1157
  return "farm-client.css";
1086
1158
  }
1087
- return "assets/[name]-[hash][extname]";
1159
+ return "assets/[name]-h[hash][extname]";
1088
1160
  }, "assetFileNames")
1089
1161
  },
1090
1162
  // Externalize Node.js built-ins and server-side modules for client build
@@ -1683,10 +1755,13 @@ document.addEventListener("click", function(e) {
1683
1755
  const imports = [];
1684
1756
  const routeEntries = [];
1685
1757
  const routeSlotEntries = [];
1686
- clientPages.forEach((page, index) => {
1758
+ clientPages.forEach((page) => {
1687
1759
  const importPath = toImportPath(page.modulePath);
1688
- imports.push(`import Page${index} from "${importPath}";`);
1689
- routeEntries.push(` { pattern: ${JSON.stringify(page.pattern)}, Component: Page${index} }`);
1760
+ routeEntries.push(` {
1761
+ pattern: ${JSON.stringify(page.pattern)},
1762
+ islandStrategy: ${JSON.stringify(page.islandStrategy)},
1763
+ load: () => import(${JSON.stringify(importPath)}).then((module) => module.default),
1764
+ }`);
1690
1765
  });
1691
1766
  clientRouteSlots.forEach((slot, index) => {
1692
1767
  const importPath = toImportPath(slot.modulePath);
@@ -1707,7 +1782,7 @@ ${cssImport}
1707
1782
  ${layoutImports}
1708
1783
  import React from "react";
1709
1784
  import { createRoot, hydrateRoot } from "react-dom/client";
1710
- import { createClientPluginManager, installChunkErrorRecovery } from "@farm.js/core/internal/client-runtime";
1785
+ import { createClientPluginManager, installChunkErrorRecovery, scheduleFarmIslandHydration } from "@farm.js/core/internal/client-runtime";
1711
1786
  import { matchFarmRoute } from "@farm.js/core/router";
1712
1787
  ${clientPluginEntry.imports}
1713
1788
  ${i18nClientRuntime}
@@ -1780,6 +1855,12 @@ function matchRoute(pathname) {
1780
1855
  return null;
1781
1856
  }
1782
1857
 
1858
+ async function loadRouteComponent(route) {
1859
+ if (route.Component) return route.Component;
1860
+ route.Component = await route.load();
1861
+ return route.Component;
1862
+ }
1863
+
1783
1864
  function matchesRoutePrefix(pathname, pattern) {
1784
1865
  if (pattern === "/") return true;
1785
1866
  const pathSegments = getFarmRoutePathname(pathname).split("/").filter(Boolean);
@@ -1922,15 +2003,24 @@ function clearRouteInterception(destination) {
1922
2003
 
1923
2004
  // State
1924
2005
  let reactRoot = null;
2006
+ let reactRootContainer = null;
1925
2007
  let currentPathname = null;
1926
2008
  let isHydrated = false;
2009
+ let pendingPageHydrationController = null;
1927
2010
 
1928
2011
  ${generateUniversalRouterStateRuntime()}
1929
2012
 
2013
+ function cancelPendingPageHydration() {
2014
+ pendingPageHydrationController?.abort();
2015
+ pendingPageHydrationController = null;
2016
+ }
2017
+
1930
2018
  function resetReactRoot() {
2019
+ cancelPendingPageHydration();
1931
2020
  if (!reactRoot) return;
1932
2021
  reactRoot.unmount();
1933
2022
  reactRoot = null;
2023
+ reactRootContainer = null;
1934
2024
  isHydrated = false;
1935
2025
  }
1936
2026
 
@@ -1944,49 +2034,77 @@ async function hydrate() {
1944
2034
  return;
1945
2035
  }
1946
2036
 
1947
- const hasRouteSlots =
1948
- Array.isArray(window.__FARM_ROUTE_SLOTS__) && window.__FARM_ROUTE_SLOTS__.length > 0;
1949
2037
  const container =
1950
- (hasRouteSlots ? document.getElementById("__farm_page__") : null) ||
2038
+ document.getElementById("__farm_page__") ||
1951
2039
  document.getElementById("root");
1952
2040
  if (!container) {
1953
2041
  console.error("[Farm.js] No root element found");
1954
2042
  return;
1955
2043
  }
1956
2044
 
1957
- const Component = matched.route.Component;
1958
- const params = matched.params;
1959
- const searchParams = Object.fromEntries(new URLSearchParams(window.location.search));
1960
-
1961
- const props = { params: params, searchParams: Promise.resolve(searchParams) };
1962
-
1963
- // Create page element and wrap with layouts
1964
- const pageElement = React.createElement(Component, props);
1965
- const wrappedElement = hasRouteSlots
1966
- ? pageElement
1967
- : wrapWithLayouts(pageElement, pathname, params);
1968
-
1969
- const shouldHydrate = !isHydrated && Boolean(container.innerHTML.trim());
1970
- const hydrationSession = await farmClientRuntime.beginHydration({
1971
- container,
1972
- mode: shouldHydrate ? "hydrate" : "render",
1973
- });
2045
+ container.dataset.farmIsland = "page";
2046
+ container.dataset.farmIslandStrategy = matched.route.islandStrategy || "load";
1974
2047
 
2048
+ const hydrationController = new AbortController();
2049
+ pendingPageHydrationController = hydrationController;
1975
2050
  try {
1976
- if (shouldHydrate) {
1977
- reactRoot = hydrateRoot(container, wrappedElement);
1978
- isHydrated = true;
1979
- } else {
1980
- if (!reactRoot) {
1981
- reactRoot = createRoot(container);
1982
- }
1983
- reactRoot.render(wrappedElement);
2051
+ await scheduleFarmIslandHydration({
2052
+ container,
2053
+ strategy: matched.route.islandStrategy,
2054
+ signal: hydrationController.signal,
2055
+ hydrate: async function() {
2056
+ const Component = await loadRouteComponent(matched.route);
2057
+ if (
2058
+ hydrationController.signal.aborted ||
2059
+ !container.isConnected ||
2060
+ window.location.pathname !== pathname
2061
+ ) {
2062
+ return;
2063
+ }
2064
+ const params = matched.params;
2065
+ const searchParams = Object.fromEntries(new URLSearchParams(window.location.search));
2066
+ const props = { params: params, searchParams: Promise.resolve(searchParams) };
2067
+ const pageElement = React.createElement(Component, props);
2068
+ const wrappedElement = container.id === "__farm_page__"
2069
+ ? pageElement
2070
+ : wrapWithLayouts(pageElement, pathname, params);
2071
+ const shouldHydrate = !isHydrated && Boolean(container.innerHTML.trim());
2072
+ const hydrationSession = await farmClientRuntime.beginHydration({
2073
+ container,
2074
+ mode: shouldHydrate ? "hydrate" : "render",
2075
+ });
2076
+
2077
+ try {
2078
+ if (hydrationController.signal.aborted || !container.isConnected) {
2079
+ await farmClientRuntime.failHydration(
2080
+ hydrationSession,
2081
+ new DOMException("Route hydration was cancelled", "AbortError"),
2082
+ );
2083
+ return;
2084
+ }
2085
+ if (shouldHydrate) {
2086
+ reactRoot = hydrateRoot(container, wrappedElement);
2087
+ reactRootContainer = container;
2088
+ isHydrated = true;
2089
+ } else {
2090
+ if (!reactRoot) {
2091
+ reactRoot = createRoot(container);
2092
+ reactRootContainer = container;
2093
+ }
2094
+ reactRoot.render(wrappedElement);
2095
+ }
2096
+ currentPathname = pathname;
2097
+ await farmClientRuntime.completeHydration(hydrationSession);
2098
+ } catch (error) {
2099
+ await farmClientRuntime.failHydration(hydrationSession, error);
2100
+ console.error("[Farm.js] Hydration error:", error);
2101
+ }
2102
+ },
2103
+ });
2104
+ } finally {
2105
+ if (pendingPageHydrationController === hydrationController) {
2106
+ pendingPageHydrationController = null;
1984
2107
  }
1985
- currentPathname = pathname;
1986
- await farmClientRuntime.completeHydration(hydrationSession);
1987
- } catch (error) {
1988
- await farmClientRuntime.failHydration(hydrationSession, error);
1989
- console.error("[Farm.js] Hydration error:", error);
1990
2108
  }
1991
2109
  }
1992
2110
 
@@ -2015,6 +2133,8 @@ ${generateUniversalRouterStateProperties()}
2015
2133
  const from = this.currentPath;
2016
2134
  if (await this.shouldBlockNavigation({ from, to, action })) return;
2017
2135
 
2136
+ // A route transition invalidates any trigger waiting on the previous DOM boundary.
2137
+ cancelPendingPageHydration();
2018
2138
  this.saveScrollPosition(window.location.pathname);
2019
2139
  this.startNavigation(from, url, action);
2020
2140
  let clientNavigation;
@@ -2081,7 +2201,9 @@ ${generateUniversalRouterStateProperties()}
2081
2201
  }
2082
2202
 
2083
2203
  if (matched) {
2084
- const Component = matched.route.Component;
2204
+ // Navigation itself signals intent, so destination routes load eagerly even
2205
+ // when their initial document hydration strategy is deferred.
2206
+ const Component = await loadRouteComponent(matched.route);
2085
2207
  const params = matched.params;
2086
2208
  const searchParams = Object.fromEntries(url.searchParams);
2087
2209
  const props = { params: params, searchParams: Promise.resolve(searchParams) };
@@ -2107,8 +2229,10 @@ ${generateUniversalRouterStateProperties()}
2107
2229
 
2108
2230
  const container = document.getElementById("root");
2109
2231
  if (container) {
2232
+ if (reactRoot && reactRootContainer !== container) resetReactRoot();
2110
2233
  if (!reactRoot) {
2111
2234
  reactRoot = createRoot(container);
2235
+ reactRootContainer = container;
2112
2236
  }
2113
2237
  reactRoot.render(wrappedElement);
2114
2238
  currentPathname = pathname;
@@ -2118,7 +2242,7 @@ ${generateUniversalRouterStateProperties()}
2118
2242
  const html = await this.fetchPage(url.pathname + url.search);
2119
2243
  await farmClientRuntime.markNavigationLoaded(clientNavigation, html);
2120
2244
  await this.runViewTransition(options.viewTransition, async () => {
2121
- if (!this.swapContent(html, url.pathname + url.search)) {
2245
+ if (!(await this.swapContent(html, url.pathname + url.search))) {
2122
2246
  throw new Error("Farm could not swap the target document");
2123
2247
  }
2124
2248
  const historyState = createHistoryState(
@@ -2173,7 +2297,7 @@ ${generateUniversalRouterStateProperties()}
2173
2297
  return html;
2174
2298
  },
2175
2299
 
2176
- swapContent: function(html, targetPath) {
2300
+ swapContent: async function(html, targetPath) {
2177
2301
  const parser = new DOMParser();
2178
2302
  const doc = parser.parseFromString(html, "text/html");
2179
2303
  if (isFarmLocaleDocumentChange(doc)) return false;
@@ -2213,18 +2337,18 @@ ${generateUniversalRouterStateProperties()}
2213
2337
  const matched = matchRoute(newPathname);
2214
2338
  if (matched) {
2215
2339
  // Re-hydrate the client component
2216
- const Component = matched.route.Component;
2340
+ const Component = await loadRouteComponent(matched.route);
2217
2341
  const params = matched.params;
2218
2342
  const searchParams = Object.fromEntries(targetUrl.searchParams);
2219
2343
  const props = { params: params, searchParams: Promise.resolve(searchParams) };
2220
2344
  const pageElement = React.createElement(Component, props);
2221
- const hasRouteSlots = nextRouteSlots.length > 0;
2222
2345
  const pageContainer =
2223
- (hasRouteSlots ? document.getElementById("__farm_page__") : null) || currentRoot;
2224
- const wrappedElement = hasRouteSlots
2346
+ document.getElementById("__farm_page__") || currentRoot;
2347
+ const wrappedElement = pageContainer.id === "__farm_page__"
2225
2348
  ? pageElement
2226
2349
  : wrapWithLayouts(pageElement, newPathname, params);
2227
2350
  reactRoot = hydrateRoot(pageContainer, wrappedElement);
2351
+ reactRootContainer = pageContainer;
2228
2352
  isHydrated = true;
2229
2353
  }
2230
2354
  return true;
@@ -2515,6 +2639,10 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2515
2639
  i18nCatalogs
2516
2640
  );
2517
2641
  const virtualEntryId = "\0virtual:farm-ssr-entry";
2642
+ const useExternalMetadataImageRuntime = shouldUseExternalMetadataImageRuntime(
2643
+ preset,
2644
+ metadataImageRoutes.some((image) => image.sourceType === "module")
2645
+ );
2518
2646
  try {
2519
2647
  await viteBuild({
2520
2648
  root,
@@ -2535,6 +2663,7 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2535
2663
  external: [
2536
2664
  "fsevents",
2537
2665
  "sharp",
2666
+ ...useExternalMetadataImageRuntime ? ["@vercel/og"] : [],
2538
2667
  "@prisma/client",
2539
2668
  "@prisma/client/default",
2540
2669
  "@prisma/client/default.js",
@@ -2563,6 +2692,7 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2563
2692
  external: [
2564
2693
  "fsevents",
2565
2694
  "sharp",
2695
+ ...useExternalMetadataImageRuntime ? ["@vercel/og"] : [],
2566
2696
  "esbuild",
2567
2697
  "lightningcss",
2568
2698
  "rollup",
@@ -2589,7 +2719,12 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2589
2719
  // React runtime. Leaving a React component package external can make it
2590
2720
  // resolve a second React instance at runtime and break hooks during SSR.
2591
2721
  // Native and build-only packages above remain explicitly external.
2592
- noExternal: preset === "cloudflare-module" ? ["@farm.js/core", "@farm.js/core/image", "better-call"] : true
2722
+ noExternal: preset === "cloudflare-module" ? [
2723
+ "@farm.js/core",
2724
+ "@farm.js/core/image",
2725
+ "better-call",
2726
+ ...metadataImageRoutes.some((image) => image.sourceType === "module") ? ["@vercel/og"] : []
2727
+ ] : true
2593
2728
  },
2594
2729
  define: {
2595
2730
  __FARM_ENV__: JSON.stringify(config.env || { server: {}, public: {} }),
@@ -2597,6 +2732,7 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2597
2732
  },
2598
2733
  plugins: [
2599
2734
  ...tailwindVitePlugin ? [tailwindVitePlugin] : [],
2735
+ ...!useExternalMetadataImageRuntime && metadataImageRoutes.some((image) => image.sourceType === "module") ? [createMetadataImageWasmPlugin()] : [],
2600
2736
  {
2601
2737
  name: "farm-react-production-mode",
2602
2738
  enforce: "pre",
@@ -2675,8 +2811,12 @@ async function buildSSRInMemory(productionVite, config, root, routeManager, apiR
2675
2811
  __name(buildSSRInMemory, "buildSSRInMemory");
2676
2812
  function generateVirtualEntryCode(apiRoutes, pageRoutes, layoutRoutes, routeSlots, errorRoutes, metadataImageRoutes, middlewareRoutes, redirectRoutes, configuredRewriteRoutes, configuredHeaderRoutes, notFoundPath, config, configModulePath, hasServerRuntimeIntegrations, hasRuntimeIntegrationConfig, hasConfiguredRuntimePlugins, preset, i18nCatalogs) {
2677
2813
  const hasPluginRuntime = hasRuntimeIntegrationConfig || hasConfiguredRuntimePlugins;
2814
+ const hasGeneratedMetadataImages = metadataImageRoutes.some(
2815
+ (image) => image.sourceType === "module"
2816
+ );
2678
2817
  const hasMiddlewareRuntime = middlewareRoutes.length > 0 || hasFarmMiddlewareConfig(config.middleware);
2679
2818
  const farmDocsBaseConfig = config.docs?.enabled ? { ...config.docs, config: void 0 } : void 0;
2819
+ const farmDocsFontAssets = config.docs?.enabled ? toFarmDocsPublicFontAssets(resolveFarmDocsFontAssets(config.root)) : [];
2680
2820
  const apiImports = [];
2681
2821
  const apiRegistrations = [];
2682
2822
  apiRoutes.forEach((route, index) => {
@@ -2715,12 +2855,13 @@ function generateVirtualEntryCode(apiRoutes, pageRoutes, layoutRoutes, routeSlot
2715
2855
  return;
2716
2856
  }
2717
2857
  pageImports.push(`import * as ${varName} from "${route.modulePath}";`);
2718
- const shouldHydrate = getClientModuleMetadata(route.modulePath, config.root).shouldHydrate;
2858
+ const clientMetadata = getClientModuleMetadata(route.modulePath, config.root);
2719
2859
  pageRegistrations.push(`
2720
2860
  {
2721
2861
  pattern: ${JSON.stringify(route.pattern)},
2722
2862
  module: ${varName},
2723
- shouldHydrate: ${JSON.stringify(shouldHydrate)},
2863
+ shouldHydrate: ${JSON.stringify(clientMetadata.shouldHydrate)},
2864
+ islandStrategy: ${JSON.stringify(clientMetadata.islandStrategy)},
2724
2865
  ${route.source !== void 0 ? `markdownSource: {
2725
2866
  source: ${JSON.stringify(route.source)},
2726
2867
  filePath: ${JSON.stringify(route.markdownSourcePath)},
@@ -2818,16 +2959,21 @@ function generateVirtualEntryCode(apiRoutes, pageRoutes, layoutRoutes, routeSlot
2818
2959
  isFarmRedirectError,
2819
2960
  localizeFarmHref,
2820
2961
  localizeFarmPathname,
2962
+ manageFarmDocumentPreloads,
2963
+ manageFarmLinkHeaderPreloads,
2821
2964
  mergeMetadata,
2822
2965
  normalizeRevalidatePath,
2966
+ reportFarmPreloadWarnings,
2823
2967
  renderMetadataHead,
2824
2968
  resolveFarmRouteContext,
2825
2969
  stripFarmLocaleFromPathname,
2826
2970
  withFarmRouteContext,
2827
2971
  } from "@farm.js/core/internal/production-runtime";`;
2972
+ const metadataImageRuntimeImport = hasGeneratedMetadataImages ? `import { createFarmMetadataImageResponse } from "@farm.js/core/internal/metadata-image-runtime";` : "";
2828
2973
  const pluginRuntimeImport = hasPluginRuntime ? `import { PluginManager } from "@farm.js/core/plugin";` : "";
2829
2974
  const i18nServerImport = config.i18n.enabled ? `import { _runWithFarmI18nRequest, _setDefaultFarmI18nRuntime, createFarmI18nRuntime, getFarmI18nClientSnapshot } from "@farm.js/core/i18n/server";` : "";
2830
2975
  const docsHandlerImport = config.docs?.enabled ? `import { createFarmDocsAPIHandler, createFarmDocsHandler, isFarmDocsAPIRequest } from "@farm.js/core/docs";` : "";
2976
+ const docsFontImport = config.docs?.enabled ? `import { resolveFarmLayoutFonts } from "@farm.js/core/font";` : "";
2831
2977
  const docsRuntimeImport = config.docs?.enabled ? `import { existsSync as farmDocsExistsSync } from "node:fs";
2832
2978
  import { dirname as farmDocsDirname, join as farmDocsJoin } from "node:path";
2833
2979
  import { fileURLToPath as farmDocsFileURLToPath } from "node:url";` : "";
@@ -2915,9 +3061,11 @@ ${middlewareImports.join("\n")}
2915
3061
  ${notFoundImport}
2916
3062
  ${apiRouteHelpersImport}
2917
3063
  ${productionRuntimeImport}
3064
+ ${metadataImageRuntimeImport}
2918
3065
  ${pluginRuntimeImport}
2919
3066
  ${i18nServerImport}
2920
3067
  ${docsHandlerImport}
3068
+ ${docsFontImport}
2921
3069
  ${docsRuntimeImport}
2922
3070
  ${markdownHandlerImport}
2923
3071
  ${appMarkdownImport}
@@ -2929,6 +3077,8 @@ import { farmFontPreloadHeader } from "virtual:farm-font-runtime";
2929
3077
  import * as React from "react";
2930
3078
  import * as ReactDOMServer from "react-dom/server";
2931
3079
 
3080
+ const farmPreloadConfig = ${JSON.stringify(config.performance.preload)};
3081
+
2932
3082
  // Custom 404 page component (if provided)
2933
3083
  const hasCustomNotFound = ${notFoundPath ? "true" : "false"};
2934
3084
  const CustomNotFoundComponent = ${notFoundPath ? "CustomNotFound.default || CustomNotFound" : "null"};
@@ -3020,7 +3170,17 @@ globalThis.__FARM_DOCS_RUNTIME_CONFIG__ = {
3020
3170
  srcDir: ${JSON.stringify(config.srcDir)},
3021
3171
  docs: farmDocsResolvedConfig,
3022
3172
  };
3023
- const farmDocsHandler = ${config.docs?.enabled ? `createFarmDocsHandler(farmDocsResolvedConfig, { root: farmDocsRuntimeRoot, srcDir: ${JSON.stringify(config.srcDir)}, clientEntry: "/farm-client.js" })` : "null"};
3173
+ const farmDocsHandler = ${config.docs?.enabled ? `createFarmDocsHandler(farmDocsResolvedConfig, {
3174
+ root: farmDocsRuntimeRoot,
3175
+ srcDir: ${JSON.stringify(config.srcDir)},
3176
+ clientEntry: "/farm-client.js",
3177
+ fontAssets: ${JSON.stringify(farmDocsFontAssets)},
3178
+ resolveLayoutFonts: (pathname) =>
3179
+ resolveFarmLayoutFonts(
3180
+ getApplicableLayouts(getFarmRoutePathname(pathname)).map((layout) => layout.module),
3181
+ ),
3182
+ fontStylesheetHref: "/farm-fonts.css",
3183
+ })` : "null"};
3024
3184
  const farmDocsAPIHandler = ${config.docs?.enabled ? `createFarmDocsAPIHandler({ rootDir: farmDocsRuntimeRoot, srcDir: ${JSON.stringify(config.srcDir)}, docs: farmDocsResolvedConfig })` : "null"};
3025
3185
 
3026
3186
  // API routes bundled at build time
@@ -3681,30 +3841,10 @@ async function handleMetadataImageRequest(request, routePathname) {
3681
3841
  ? await imageModule.default(props)
3682
3842
  : imageModule.default;
3683
3843
 
3684
- if (value instanceof Response) {
3685
- return method === "HEAD"
3686
- ? new Response(null, { status: value.status, headers: value.headers })
3687
- : value;
3688
- }
3689
-
3690
- let body;
3691
- if (typeof value === "string" || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
3692
- body = value;
3693
- } else {
3694
- if (!React.isValidElement(value)) {
3695
- throw new Error("Metadata image must return a Response, string, bytes, or React element");
3696
- }
3697
- body = ReactDOMServer.renderToStaticMarkup(value);
3698
- }
3699
-
3700
- return new Response(method === "HEAD" ? null : body, {
3701
- status: 200,
3702
- headers: {
3703
- "Content-Type": imageModule.contentType || "image/svg+xml; charset=utf-8",
3704
- "Cache-Control": "public, max-age=0, must-revalidate",
3705
- "X-Content-Type-Options": "nosniff",
3706
- },
3707
- });
3844
+ ${hasGeneratedMetadataImages ? `return createFarmMetadataImageResponse(value, imageModule, {
3845
+ method,
3846
+ ifNoneMatch: request.headers.get("if-none-match"),
3847
+ });` : 'throw new Error("Generated metadata image runtime is unavailable");'}
3708
3848
  } catch (error) {
3709
3849
  console.error("Metadata image render failed:", error);
3710
3850
  return new Response("Internal Server Error", {
@@ -4240,7 +4380,16 @@ async function handleFarmRequestInContext(
4240
4380
  if (farmDocsHandler) {
4241
4381
  const docsResponse = await farmDocsHandler(request.clone());
4242
4382
  if (docsResponse) {
4243
- return docsResponse;
4383
+ if (!docsResponse.headers.get("content-type")?.toLowerCase().includes("text/html")) {
4384
+ return docsResponse;
4385
+ }
4386
+ const docsHeaders = new Headers(docsResponse.headers);
4387
+ docsHeaders.set("x-farm-preload-buffered", "1");
4388
+ return new Response(docsResponse.body, {
4389
+ status: docsResponse.status,
4390
+ statusText: docsResponse.statusText,
4391
+ headers: docsHeaders,
4392
+ });
4244
4393
  }
4245
4394
  }
4246
4395
  ` : ""}
@@ -4368,6 +4517,7 @@ async function handleFarmRequestInContext(
4368
4517
  status: 200,
4369
4518
  headers: {
4370
4519
  "Content-Type": "text/html; charset=utf-8",
4520
+ "x-farm-preload-buffered": "1",
4371
4521
  ...(farmFontPreloadHeader ? { "Link": farmFontPreloadHeader } : {}),
4372
4522
  ...getPPRHeaders("hit", pprConfig),
4373
4523
  },
@@ -4595,10 +4745,15 @@ async function handleFarmRequestInContext(
4595
4745
  pageElement = React.createElement(PageComponent, pageProps);
4596
4746
  }
4597
4747
 
4598
- if (route.shouldHydrate && renderedRouteSlots.length > 0) {
4748
+ if (route.shouldHydrate) {
4599
4749
  pageElement = React.createElement(
4600
4750
  "div",
4601
- { id: "__farm_page__", "data-farm-client": "true" },
4751
+ {
4752
+ id: "__farm_page__",
4753
+ "data-farm-client": "true",
4754
+ "data-farm-island": "page",
4755
+ "data-farm-island-strategy": route.islandStrategy || "load",
4756
+ },
4602
4757
  pageElement,
4603
4758
  );
4604
4759
  }
@@ -4749,7 +4904,7 @@ async function handleFarmRequestInContext(
4749
4904
  );
4750
4905
  return applyProductionMiddlewareHeaders(new Response(streamedDocument, {
4751
4906
  status: pageStatus,
4752
- headers: responseHeaders,
4907
+ headers: { ...responseHeaders, "x-farm-preload-streaming": "1" },
4753
4908
  }), middlewareHeaders);
4754
4909
  }
4755
4910
 
@@ -4850,7 +5005,7 @@ async function handleFarmRequestInContext(
4850
5005
  fullHtml,
4851
5006
  {
4852
5007
  status: pageStatus,
4853
- headers: responseHeaders
5008
+ headers: { ...responseHeaders, "x-farm-preload-buffered": "1" }
4854
5009
  }
4855
5010
  ), middlewareHeaders);
4856
5011
  }
@@ -4954,6 +5109,7 @@ async function handleFarmRequestInContext(
4954
5109
  headers: {
4955
5110
  "Content-Type": "text/html; charset=utf-8",
4956
5111
  "Cache-Control": "private, no-store",
5112
+ "x-farm-preload-buffered": "1",
4957
5113
  },
4958
5114
  }), middlewareHeaders);
4959
5115
  } catch (boundaryError) {
@@ -4976,6 +5132,7 @@ async function handleFarmRequestInContext(
4976
5132
  headers: {
4977
5133
  "Content-Type": "text/html; charset=utf-8",
4978
5134
  "Cache-Control": "private, no-store",
5135
+ "x-farm-preload-buffered": "1",
4979
5136
  },
4980
5137
  }
4981
5138
  ), middlewareHeaders);
@@ -5122,13 +5279,19 @@ async function handleFarmRequestInContext(
5122
5279
 
5123
5280
  return applyProductionMiddlewareHeaders(new Response(fullHtml, {
5124
5281
  status: 404,
5125
- headers: { "Content-Type": "text/html; charset=utf-8" }
5282
+ headers: {
5283
+ "Content-Type": "text/html; charset=utf-8",
5284
+ "x-farm-preload-buffered": "1",
5285
+ }
5126
5286
  }), middlewareHeaders);
5127
5287
  } catch (error) {
5128
5288
  console.error("404 render error:", error);
5129
5289
  return applyProductionMiddlewareHeaders(new Response(
5130
5290
  \`<!DOCTYPE html><html><head><title>404</title></head><body><h1>404 - Page Not Found</h1><p>The page \${pathname} doesn't exist.</p><a href="/">Go Home</a></body></html>\`,
5131
- { status: 404, headers: { "Content-Type": "text/html" } }
5291
+ {
5292
+ status: 404,
5293
+ headers: { "Content-Type": "text/html", "x-farm-preload-buffered": "1" },
5294
+ }
5132
5295
  ), middlewareHeaders);
5133
5296
  }
5134
5297
  ${hasMiddlewareRuntime ? `
@@ -5170,7 +5333,10 @@ async function handleFarmPluginRequest(request, runtimeOptions) {
5170
5333
  await farmPluginRuntime.runHookParallel("beforeRender", renderPayload);
5171
5334
 
5172
5335
  const response = await handleFarmRequest(request);
5173
- if (!hasFarmPluginHTMLTransforms || !response.headers.get("content-type")?.includes("text/html")) {
5336
+ if (
5337
+ !hasFarmPluginHTMLTransforms ||
5338
+ !response.headers.get("content-type")?.toLowerCase().includes("text/html")
5339
+ ) {
5174
5340
  return response;
5175
5341
  }
5176
5342
 
@@ -5180,6 +5346,8 @@ async function handleFarmPluginRequest(request, runtimeOptions) {
5180
5346
 
5181
5347
  const headers = new Headers(response.headers);
5182
5348
  headers.delete("content-length");
5349
+ headers.delete("x-farm-preload-streaming");
5350
+ headers.set("x-farm-preload-buffered", "1");
5183
5351
  return new Response(html, {
5184
5352
  status: response.status,
5185
5353
  statusText: response.statusText,
@@ -5187,6 +5355,49 @@ async function handleFarmPluginRequest(request, runtimeOptions) {
5187
5355
  });
5188
5356
  }
5189
5357
 
5358
+ async function applyFarmPreloadBudget(response, pathname) {
5359
+ const headers = new Headers(response.headers);
5360
+ const linkHeader = headers.get("Link") || "";
5361
+ const isHtml = headers.get("Content-Type")?.toLowerCase().includes("text/html");
5362
+ const isStreaming = headers.get("x-farm-preload-streaming") === "1";
5363
+ const isBuffered = headers.get("x-farm-preload-buffered") === "1";
5364
+ headers.delete("x-farm-preload-streaming");
5365
+ headers.delete("x-farm-preload-buffered");
5366
+
5367
+ if (!isHtml) {
5368
+ if (!isStreaming && !isBuffered) return response;
5369
+ return new Response(response.body, {
5370
+ status: response.status,
5371
+ statusText: response.statusText,
5372
+ headers,
5373
+ });
5374
+ }
5375
+
5376
+ if (isStreaming || !isBuffered || response.body === null) {
5377
+ const managed = manageFarmLinkHeaderPreloads(linkHeader, farmPreloadConfig);
5378
+ if (managed.value) headers.set("Link", managed.value);
5379
+ else headers.delete("Link");
5380
+ reportFarmPreloadWarnings(managed.warnings, "route " + pathname);
5381
+ return new Response(response.body, {
5382
+ status: response.status,
5383
+ statusText: response.statusText,
5384
+ headers,
5385
+ });
5386
+ }
5387
+
5388
+ const html = await response.text();
5389
+ const managed = manageFarmDocumentPreloads(html, linkHeader, farmPreloadConfig);
5390
+ if (managed.linkHeader) headers.set("Link", managed.linkHeader);
5391
+ else headers.delete("Link");
5392
+ if (managed.html !== html) headers.delete("Content-Length");
5393
+ reportFarmPreloadWarnings(managed.warnings, "route " + pathname);
5394
+ return new Response(managed.html, {
5395
+ status: response.status,
5396
+ statusText: response.statusText,
5397
+ headers,
5398
+ });
5399
+ }
5400
+
5190
5401
  // Export as Web Standard fetch API
5191
5402
  export async function fetch(request, context) {
5192
5403
  const runtimeOptions = farmPluginRuntime ? getFarmPluginRequestOptions(request) : null;
@@ -5209,7 +5420,8 @@ export async function fetch(request, context) {
5209
5420
  const response = await _runWithCurrentRequest(request, () =>
5210
5421
  _runWithAfterRequest(request, runRequest, context),
5211
5422
  );
5212
- return applyConfiguredResponseHeaders(response, new URL(request.url).pathname);
5423
+ const pathname = new URL(request.url).pathname;
5424
+ return applyFarmPreloadBudget(applyConfiguredResponseHeaders(response, pathname), pathname);
5213
5425
  }
5214
5426
  export default { fetch };
5215
5427
  `.trim();
@@ -5365,8 +5577,19 @@ async function buildNitroUniversal(config, routeManager, apiRouteManager, server
5365
5577
  const outputDir = resolveDeployOutputPath(root, config.deploy.outputDir);
5366
5578
  const ssrOutputDir = path3.join(root, distDir, "ssr");
5367
5579
  const imageRuntime = resolveImageRuntime(config, preset);
5580
+ const hasGeneratedMetadataImages = Array.from(routeManager.getMetadataImages().values()).some(
5581
+ (image) => image.sourceType === "module"
5582
+ );
5583
+ const useExternalMetadataImageRuntime = shouldUseExternalMetadataImageRuntime(
5584
+ preset,
5585
+ hasGeneratedMetadataImages
5586
+ );
5587
+ const nitroRollupExternal = /* @__PURE__ */ __name((id) => useExternalMetadataImageRuntime && id === "@vercel/og" || isNitroRollupExternal(id), "nitroRollupExternal");
5368
5588
  const ssrExternalPackages = collectSSRExternalPackages(ssrBundle);
5369
- const copiedRuntimePackages = new Set(imageRuntime === "node" ? ["sharp"] : []);
5589
+ const copiedRuntimePackages = /* @__PURE__ */ new Set([
5590
+ ...imageRuntime === "node" ? ["sharp"] : [],
5591
+ ...useExternalMetadataImageRuntime ? ["@vercel/og"] : []
5592
+ ]);
5370
5593
  const isPrebuiltSSRCandidate = preset === "node-server" && [...ssrExternalPackages].every((packageName) => copiedRuntimePackages.has(packageName));
5371
5594
  logger.info(`\u{1F4E6} Nitro output directory: ${outputDir}`);
5372
5595
  logger.info(`\u{1F4E6} SSR entry file: ${ssrEntryFile}`);
@@ -5582,11 +5805,13 @@ export default async function farmNitroEventHandler(event) {
5582
5805
  ".prisma/client",
5583
5806
  ".prisma/client/default",
5584
5807
  "better-sqlite3",
5585
- "sharp"
5808
+ "sharp",
5809
+ ...useExternalMetadataImageRuntime ? ["@vercel/og"] : []
5586
5810
  ]
5587
5811
  },
5588
5812
  rollupConfig: {
5589
- external: isNitroRollupExternal
5813
+ external: nitroRollupExternal,
5814
+ plugins: hasGeneratedMetadataImages && !useExternalMetadataImageRuntime ? [createMetadataImageWasmPlugin()] : []
5590
5815
  },
5591
5816
  // Keep the public Nitro boolean intact for build hooks. It is translated to
5592
5817
  // Nitro's existing esbuild pass after hooks run, avoiding a second Terser pass.
@@ -5614,7 +5839,7 @@ export default async function farmNitroEventHandler(event) {
5614
5839
  snapshotSSRRebundleOptions(nitroConfig)
5615
5840
  );
5616
5841
  const requestedBuilder = nitroConfig.builder ?? process.env.NITRO_BUILDER;
5617
- const canReusePrebuiltSSR = isPrebuiltSSRCandidate && nitroConfig.preset === "node-server" && (requestedBuilder === void 0 || requestedBuilder === "rollup" || requestedBuilder === "rolldown") && nitroConfig.sourceMap === false && !hasUnsupportedEsbuildOverrides && !hasLateBuildMutationConfig && !hasUnsupportedSSRRebundleOverrides && nitroConfig.rollupConfig?.external === isNitroRollupExternal && !hasRollupPlugins(nitroConfig.rollupConfig?.plugins) && customRollupKeys.length === 0 && path3.resolve(nitroConfig.output?.serverDir || "") === path3.resolve(expectedServerDir) && Boolean(
5842
+ const canReusePrebuiltSSR = isPrebuiltSSRCandidate && nitroConfig.preset === "node-server" && (requestedBuilder === void 0 || requestedBuilder === "rollup" || requestedBuilder === "rolldown") && nitroConfig.sourceMap === false && !hasUnsupportedEsbuildOverrides && !hasLateBuildMutationConfig && !hasUnsupportedSSRRebundleOverrides && nitroConfig.rollupConfig?.external === nitroRollupExternal && !hasRollupPlugins(nitroConfig.rollupConfig?.plugins) && customRollupKeys.length === 0 && path3.resolve(nitroConfig.output?.serverDir || "") === path3.resolve(expectedServerDir) && Boolean(
5618
5843
  nitroConfig.handlers?.some(
5619
5844
  (handler) => path3.resolve(handler?.handler || "") === nitroEntryPath
5620
5845
  )
@@ -5702,7 +5927,8 @@ export default async function farmNitroEventHandler(event) {
5702
5927
  effectiveMinify,
5703
5928
  fs
5704
5929
  ) : Promise.resolve(),
5705
- imageRuntime === "node" ? copySharpRuntime(config, root, path3.join(outputDir, "server"), fs) : Promise.resolve()
5930
+ imageRuntime === "node" ? copySharpRuntime(config, root, path3.join(outputDir, "server"), fs) : Promise.resolve(),
5931
+ useExternalMetadataImageRuntime ? copyMetadataImageRuntime(root, path3.join(outputDir, "server"), fs) : Promise.resolve()
5706
5932
  ]);
5707
5933
  const failedCopy = copyResults.find(
5708
5934
  (result) => result.status === "rejected"
@@ -5814,6 +6040,9 @@ async function postProcessVercelOutput(root, outputDir, fs, config, farmWorkflow
5814
6040
  fs
5815
6041
  );
5816
6042
  vercelConfig.routes = [
6043
+ // Apply the header before the filesystem handler. `continue` lets Vercel
6044
+ // serve the matching file while preserving the immutable cache policy.
6045
+ createFarmVercelImmutableAssetRoute(config.basePath),
5817
6046
  // Serve static files first
5818
6047
  {
5819
6048
  handle: "filesystem"
@@ -5931,6 +6160,42 @@ async function copySharpRuntime(config, root, nitroFuncDir, fs) {
5931
6160
  logger.info(`\u{1F5BC}\uFE0F Bundled Sharp image runtime (${copiedPackages.size} packages)`);
5932
6161
  }
5933
6162
  __name(copySharpRuntime, "copySharpRuntime");
6163
+ async function copyMetadataImageRuntime(root, nitroFuncDir, fs) {
6164
+ const projectRequire = createRequire(path3.join(root, "package.json"));
6165
+ let runtimeRequire = projectRequire;
6166
+ let packageJsonPath = resolvePackageJson(runtimeRequire, "@vercel/og");
6167
+ if (!packageJsonPath) {
6168
+ try {
6169
+ runtimeRequire = createRequire(projectRequire.resolve("@farm.js/core"));
6170
+ packageJsonPath = resolvePackageJson(runtimeRequire, "@vercel/og");
6171
+ } catch {
6172
+ }
6173
+ }
6174
+ if (!packageJsonPath) {
6175
+ throw new Error(
6176
+ "Generated metadata images require @vercel/og. Reinstall @farm.js/core with production dependencies."
6177
+ );
6178
+ }
6179
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
6180
+ const packageDir = path3.dirname(packageJsonPath);
6181
+ const targetDir = path3.join(nitroFuncDir, "node_modules", "@vercel", "og");
6182
+ await fs.mkdir(path3.dirname(targetDir), { recursive: true });
6183
+ await fs.cp(packageDir, targetDir, {
6184
+ recursive: true,
6185
+ force: true,
6186
+ dereference: true,
6187
+ mode: fsConstants.COPYFILE_FICLONE
6188
+ });
6189
+ const functionPackagePath = path3.join(nitroFuncDir, "package.json");
6190
+ const functionPackage = JSON.parse(await fs.readFile(functionPackagePath, "utf8"));
6191
+ functionPackage.dependencies = {
6192
+ ...functionPackage.dependencies,
6193
+ "@vercel/og": String(packageJson.version || "0.11.1")
6194
+ };
6195
+ await fs.writeFile(functionPackagePath, JSON.stringify(functionPackage, null, 2));
6196
+ logger.info("\u{1F5BC}\uFE0F Bundled generated metadata image runtime");
6197
+ }
6198
+ __name(copyMetadataImageRuntime, "copyMetadataImageRuntime");
5934
6199
  function resolvePackageJson(parentRequire, packageName) {
5935
6200
  for (const request of [`${packageName}/package.json`, `${packageName}/package`]) {
5936
6201
  try {
@@ -6320,4 +6585,4 @@ __name(buildNitro, "buildNitro");
6320
6585
  export {
6321
6586
  build
6322
6587
  };
6323
- //# sourceMappingURL=chunk-DTOWEQRV.mjs.map
6588
+ //# sourceMappingURL=chunk-VSDZEB5M.mjs.map