webstudio 0.267.0 → 0.269.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webstudio",
3
- "version": "0.267.0",
3
+ "version": "0.269.0",
4
4
  "description": "Webstudio CLI",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -43,8 +43,8 @@
43
43
  "warn-once": "^0.1.1",
44
44
  "yargs": "^17.7.2",
45
45
  "zod": "^3.24.2",
46
- "@webstudio-is/project-migrations": "0.267.0",
47
- "@webstudio-is/trpc-interface": "0.267.0"
46
+ "@webstudio-is/project-migrations": "0.269.0",
47
+ "@webstudio-is/trpc-interface": "0.269.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@cloudflare/vite-plugin": "^1.1.0",
@@ -74,17 +74,18 @@
74
74
  "vite": "^6.3.4",
75
75
  "vitest": "^3.1.2",
76
76
  "wrangler": "^3.63.2",
77
- "@webstudio-is/css-engine": "0.267.0",
78
- "@webstudio-is/http-client": "0.267.0",
79
- "@webstudio-is/react-sdk": "0.267.0",
80
- "@webstudio-is/image": "0.267.0",
81
- "@webstudio-is/sdk": "0.267.0",
82
- "@webstudio-is/sdk-components-animation": "0.267.0",
83
- "@webstudio-is/sdk-components-react": "0.267.0",
84
- "@webstudio-is/sdk-components-react-radix": "0.267.0",
85
- "@webstudio-is/sdk-components-react-router": "0.267.0",
86
- "@webstudio-is/sdk-components-react-remix": "0.267.0",
87
- "@webstudio-is/tsconfig": "1.0.7"
77
+ "@webstudio-is/http-client": "0.269.0",
78
+ "@webstudio-is/image": "0.269.0",
79
+ "@webstudio-is/react-sdk": "0.269.0",
80
+ "@webstudio-is/sdk-components-animation": "0.269.0",
81
+ "@webstudio-is/css-engine": "0.269.0",
82
+ "@webstudio-is/sdk": "0.269.0",
83
+ "@webstudio-is/sdk-components-react": "0.269.0",
84
+ "@webstudio-is/sdk-components-react-radix": "0.269.0",
85
+ "@webstudio-is/sdk-components-react-remix": "0.269.0",
86
+ "@webstudio-is/sdk-components-react-router": "0.269.0",
87
+ "@webstudio-is/tsconfig": "1.0.7",
88
+ "@webstudio-is/wsauth": "0.269.0"
88
89
  },
89
90
  "scripts": {
90
91
  "typecheck": "tsgo --noEmit",
@@ -12,6 +12,7 @@
12
12
  "build-cf-types": "wrangler types"
13
13
  },
14
14
  "dependencies": {
15
+ "@webstudio-is/wsauth": "0.269.0",
15
16
  "@remix-run/cloudflare": "2.16.5",
16
17
  "@remix-run/cloudflare-pages": "2.16.5"
17
18
  },
@@ -0,0 +1,21 @@
1
+ import { createPath, generatePath, parsePath } from "@remix-run/react";
2
+
3
+ /**
4
+ * Expands route params in local redirect targets.
5
+ * External and protocol-relative URLs are returned unchanged because route params
6
+ * only apply to app paths.
7
+ */
8
+ export const generateRedirectUrl = (
9
+ url: string,
10
+ params: Record<string, string | undefined>
11
+ ) => {
12
+ if (url.startsWith("/") === false || url.startsWith("//")) {
13
+ return url;
14
+ }
15
+
16
+ const path = parsePath(url);
17
+ return createPath({
18
+ ...path,
19
+ pathname: generatePath(path.pathname ?? "/", params),
20
+ });
21
+ };
@@ -1,10 +1,19 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
2
 
3
3
  import { Links, Meta, Outlet, useMatches } from "@remix-run/react";
4
+ import type { HeadersFunction } from "@remix-run/server-runtime";
4
5
  // @todo think about how to make __generated__ typeable
5
6
  // @ts-ignore
6
7
  import { CustomCode, projectId, lastPublished } from "./__generated__/_index";
7
8
 
9
+ export const headers: HeadersFunction = ({ errorHeaders }) => {
10
+ if (errorHeaders) {
11
+ return errorHeaders;
12
+ }
13
+
14
+ return {};
15
+ };
16
+
8
17
  const Root = () => {
9
18
  // Get language from matches
10
19
  const matches = useMatches();
@@ -1,3 +1,4 @@
1
+ import { type ComponentProps, memo, useMemo } from "react";
1
2
  import {
2
3
  type ServerRuntimeMetaFunction as MetaFunction,
3
4
  type LinksFunction,
@@ -17,6 +18,7 @@ import {
17
18
  formIdFieldName,
18
19
  formBotFieldName,
19
20
  } from "@webstudio-is/sdk/runtime";
21
+ import { authenticateRequest } from "@webstudio-is/wsauth";
20
22
  import {
21
23
  ReactSdkContext,
22
24
  PageSettingsMeta,
@@ -25,6 +27,7 @@ import {
25
27
  } from "@webstudio-is/react-sdk/runtime";
26
28
  import {
27
29
  projectId,
30
+ projectDomain,
28
31
  Page,
29
32
  siteName,
30
33
  favIconAsset,
@@ -42,6 +45,25 @@ import * as constants from "__CONSTANTS__";
42
45
  import css from "__CSS__?url";
43
46
  import { sitemap } from "__SITEMAP__";
44
47
  import { assets } from "__ASSETS__";
48
+ import { authRoutes } from "__AUTH__";
49
+
50
+ const authenticateProductionRequest = (request: Request) => {
51
+ const host =
52
+ request.headers.get("x-forwarded-host") ||
53
+ request.headers.get("host") ||
54
+ "";
55
+
56
+ const requestHost = host.split(":")[0];
57
+ if (
58
+ projectDomain !== undefined &&
59
+ (requestHost === projectDomain ||
60
+ requestHost.startsWith(`${projectDomain}.`))
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ return authenticateRequest(request, authRoutes);
66
+ };
45
67
 
46
68
  const customFetch: typeof fetch = (input, init) => {
47
69
  if (typeof input !== "string") {
@@ -83,6 +105,8 @@ const customFetch: typeof fetch = (input, init) => {
83
105
  };
84
106
 
85
107
  export const loader = async (arg: LoaderFunctionArgs) => {
108
+ const authRoute = authenticateProductionRequest(arg.request);
109
+
86
110
  const url = new URL(arg.request.url);
87
111
  const host =
88
112
  arg.request.headers.get("x-forwarded-host") ||
@@ -133,13 +157,18 @@ export const loader = async (arg: LoaderFunctionArgs) => {
133
157
  {
134
158
  status: pageMeta.status,
135
159
  headers: {
136
- "Cache-Control": "public, max-age=600",
160
+ "Cache-Control":
161
+ authRoute === undefined ? "public, max-age=600" : "private, no-store",
137
162
  },
138
163
  }
139
164
  );
140
165
  };
141
166
 
142
- export const headers: HeadersFunction = () => {
167
+ export const headers: HeadersFunction = ({ errorHeaders }) => {
168
+ if (errorHeaders) {
169
+ return errorHeaders;
170
+ }
171
+
143
172
  return {
144
173
  "Cache-Control": "public, max-age=0, must-revalidate",
145
174
  };
@@ -220,6 +249,8 @@ export const action = async ({
220
249
  }: ActionFunctionArgs): Promise<
221
250
  { success: true } | { success: false; errors: string[] }
222
251
  > => {
252
+ authenticateProductionRequest(request);
253
+
223
254
  try {
224
255
  const url = new URL(request.url);
225
256
  url.host = getRequestHost(request);
@@ -298,20 +329,33 @@ export const action = async ({
298
329
  }
299
330
  };
300
331
 
332
+ const PageBoundary = memo(
333
+ ({ url, system }: ComponentProps<typeof Page> & { url: string }) => {
334
+ // Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages
335
+ return <Page key={url} system={system} />;
336
+ },
337
+ // React Router can rerender the current route while the next route loaders are
338
+ // still pending. Keep the generated page out of that pending-navigation render
339
+ // path, but let URL changes remount it.
340
+ (prevProps, nextProps) => prevProps.url === nextProps.url
341
+ );
342
+
301
343
  const Outlet = () => {
302
344
  const { system, resources, url, pageMeta, host } =
303
345
  useLoaderData<typeof loader>();
346
+ const sdkContext = useMemo(
347
+ () => ({
348
+ ...constants,
349
+ resources,
350
+ breakpoints,
351
+ onError: console.error,
352
+ }),
353
+ [resources]
354
+ );
355
+
304
356
  return (
305
- <ReactSdkContext.Provider
306
- value={{
307
- ...constants,
308
- resources,
309
- breakpoints,
310
- onError: console.error,
311
- }}
312
- >
313
- {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
314
- <Page key={url} system={system} />
357
+ <ReactSdkContext.Provider value={sdkContext}>
358
+ <PageBoundary url={url} system={system} />
315
359
  <PageSettingsMeta
316
360
  url={url}
317
361
  pageMeta={pageMeta}
@@ -1,6 +1,7 @@
1
- import { redirect } from "@remix-run/server-runtime";
1
+ import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
2
+ import { generateRedirectUrl } from "../redirect-url";
2
3
  import { url, status } from "__REDIRECT__";
3
4
 
4
- export const loader = () => {
5
- return redirect(url, status);
5
+ export const loader = (arg: LoaderFunctionArgs) => {
6
+ return redirect(generateRedirectUrl(url, arg.params), status);
6
7
  };
@@ -0,0 +1,107 @@
1
+ import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
2
+ import { isLocalResource, loadResources } from "@webstudio-is/sdk/runtime";
3
+ import { authenticateRequest } from "@webstudio-is/wsauth";
4
+ import { projectDomain } from "__CLIENT__";
5
+ import { getPageMeta, getRemixParams, getResources } from "__SERVER__";
6
+ import { sitemap } from "__SITEMAP__";
7
+ import { assets } from "__ASSETS__";
8
+ import { authRoutes } from "__AUTH__";
9
+
10
+ const authenticateProductionRequest = (request: Request) => {
11
+ const host =
12
+ request.headers.get("x-forwarded-host") ||
13
+ request.headers.get("host") ||
14
+ "";
15
+
16
+ const requestHost = host.split(":")[0];
17
+ if (
18
+ projectDomain !== undefined &&
19
+ (requestHost === projectDomain ||
20
+ requestHost.startsWith(`${projectDomain}.`))
21
+ ) {
22
+ return;
23
+ }
24
+
25
+ return authenticateRequest(request, authRoutes);
26
+ };
27
+
28
+ const customFetch: typeof fetch = (input, init) => {
29
+ if (typeof input !== "string") {
30
+ return fetch(input, init);
31
+ }
32
+
33
+ if (isLocalResource(input, "sitemap.xml")) {
34
+ const response = new Response(JSON.stringify(sitemap));
35
+ response.headers.set("content-type", "application/json; charset=utf-8");
36
+ return Promise.resolve(response);
37
+ }
38
+
39
+ if (isLocalResource(input, "current-date")) {
40
+ const now = new Date();
41
+ const startOfDay = new Date(
42
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
43
+ );
44
+ const data = {
45
+ iso: startOfDay.toISOString(),
46
+ year: startOfDay.getUTCFullYear(),
47
+ month: startOfDay.getUTCMonth() + 1,
48
+ day: startOfDay.getUTCDate(),
49
+ timestamp: startOfDay.getTime(),
50
+ };
51
+ const response = new Response(JSON.stringify(data));
52
+ response.headers.set("content-type", "application/json; charset=utf-8");
53
+ return Promise.resolve(response);
54
+ }
55
+
56
+ if (isLocalResource(input, "assets")) {
57
+ const response = new Response(JSON.stringify(assets));
58
+ response.headers.set("content-type", "application/json; charset=utf-8");
59
+ return Promise.resolve(response);
60
+ }
61
+
62
+ return fetch(input, init);
63
+ };
64
+
65
+ export const loader = async (arg: LoaderFunctionArgs) => {
66
+ const authRoute = authenticateProductionRequest(arg.request);
67
+
68
+ const url = new URL(arg.request.url);
69
+ const host =
70
+ arg.request.headers.get("x-forwarded-host") ||
71
+ arg.request.headers.get("host") ||
72
+ "";
73
+ url.host = host;
74
+ url.protocol = "https";
75
+
76
+ const params = getRemixParams(arg.params);
77
+
78
+ const system = {
79
+ params,
80
+ search: Object.fromEntries(url.searchParams),
81
+ origin: url.origin,
82
+ pathname: url.pathname,
83
+ };
84
+
85
+ const resources = await loadResources(
86
+ customFetch,
87
+ getResources({ system }).data
88
+ );
89
+ const pageMeta = getPageMeta({ system, resources });
90
+
91
+ if (pageMeta.redirect) {
92
+ const status =
93
+ pageMeta.status === 301 || pageMeta.status === 302
94
+ ? pageMeta.status
95
+ : 302;
96
+ return redirect(pageMeta.redirect, status);
97
+ }
98
+
99
+ return new Response(pageMeta.content ?? "", {
100
+ status: pageMeta.status,
101
+ headers: {
102
+ "Content-Type": "text/plain; charset=utf-8",
103
+ "Cache-Control":
104
+ authRoute === undefined ? "public, max-age=600" : "private, no-store",
105
+ },
106
+ });
107
+ };
@@ -1,15 +1,35 @@
1
1
  import { renderToString } from "react-dom/server";
2
2
  import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
3
3
  import { isLocalResource, loadResources } from "@webstudio-is/sdk/runtime";
4
+ import { authenticateRequest } from "@webstudio-is/wsauth";
4
5
  import {
5
6
  ReactSdkContext,
6
7
  xmlNodeTagSuffix,
7
8
  } from "@webstudio-is/react-sdk/runtime";
8
- import { Page, breakpoints } from "__CLIENT__";
9
+ import { Page, breakpoints, projectDomain } from "__CLIENT__";
9
10
  import { getPageMeta, getRemixParams, getResources } from "__SERVER__";
10
11
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
11
12
  import { sitemap } from "__SITEMAP__";
12
13
  import { assets } from "__ASSETS__";
14
+ import { authRoutes } from "__AUTH__";
15
+
16
+ const authenticateProductionRequest = (request: Request) => {
17
+ const host =
18
+ request.headers.get("x-forwarded-host") ||
19
+ request.headers.get("host") ||
20
+ "";
21
+
22
+ const requestHost = host.split(":")[0];
23
+ if (
24
+ projectDomain !== undefined &&
25
+ (requestHost === projectDomain ||
26
+ requestHost.startsWith(`${projectDomain}.`))
27
+ ) {
28
+ return;
29
+ }
30
+
31
+ return authenticateRequest(request, authRoutes);
32
+ };
13
33
 
14
34
  const customFetch: typeof fetch = (input, init) => {
15
35
  if (typeof input !== "string") {
@@ -51,6 +71,8 @@ const customFetch: typeof fetch = (input, init) => {
51
71
  };
52
72
 
53
73
  export const loader = async (arg: LoaderFunctionArgs) => {
74
+ const authRoute = authenticateProductionRequest(arg.request);
75
+
54
76
  const url = new URL(arg.request.url);
55
77
  const host =
56
78
  arg.request.headers.get("x-forwarded-host") ||
@@ -104,6 +126,10 @@ export const loader = async (arg: LoaderFunctionArgs) => {
104
126
  text = text.replaceAll(xmlNodeTagSuffix, "");
105
127
 
106
128
  return new Response(`<?xml version="1.0" encoding="UTF-8"?>\n${text}`, {
107
- headers: { "Content-Type": "application/xml" },
129
+ headers: {
130
+ "Content-Type": "application/xml",
131
+ "Cache-Control":
132
+ authRoute === undefined ? "public, max-age=600" : "private, no-store",
133
+ },
108
134
  });
109
135
  };
@@ -11,13 +11,14 @@
11
11
  "@remix-run/node": "2.16.5",
12
12
  "@remix-run/react": "2.16.5",
13
13
  "@remix-run/server-runtime": "2.16.5",
14
- "@webstudio-is/image": "0.267.0",
15
- "@webstudio-is/react-sdk": "0.267.0",
16
- "@webstudio-is/sdk": "0.267.0",
17
- "@webstudio-is/sdk-components-react": "0.267.0",
18
- "@webstudio-is/sdk-components-animation": "0.267.0",
19
- "@webstudio-is/sdk-components-react-radix": "0.267.0",
20
- "@webstudio-is/sdk-components-react-remix": "0.267.0",
14
+ "@webstudio-is/image": "0.269.0",
15
+ "@webstudio-is/react-sdk": "0.269.0",
16
+ "@webstudio-is/sdk": "0.269.0",
17
+ "@webstudio-is/sdk-components-react": "0.269.0",
18
+ "@webstudio-is/sdk-components-animation": "0.269.0",
19
+ "@webstudio-is/sdk-components-react-radix": "0.269.0",
20
+ "@webstudio-is/sdk-components-react-remix": "0.269.0",
21
+ "@webstudio-is/wsauth": "0.269.0",
21
22
  "isbot": "^5.1.25",
22
23
  "react": "18.3.0-canary-14898b6a9-20240318",
23
24
  "react-dom": "18.3.0-canary-14898b6a9-20240318"
@@ -6,6 +6,7 @@
6
6
  "@webstudio-is/sdk-components-animation": "workspace:*",
7
7
  "@webstudio-is/sdk-components-react": "workspace:*",
8
8
  "@webstudio-is/sdk-components-react-radix": "workspace:*",
9
- "@webstudio-is/sdk-components-react-remix": "workspace:*"
9
+ "@webstudio-is/sdk-components-react-remix": "workspace:*",
10
+ "@webstudio-is/wsauth": "workspace:*"
10
11
  }
11
12
  }
@@ -0,0 +1,21 @@
1
+ import { createPath, generatePath, parsePath } from "react-router";
2
+
3
+ /**
4
+ * Expands route params in local redirect targets.
5
+ * External and protocol-relative URLs are returned unchanged because route params
6
+ * only apply to app paths.
7
+ */
8
+ export const generateRedirectUrl = (
9
+ url: string,
10
+ params: Record<string, string | undefined>
11
+ ) => {
12
+ if (url.startsWith("/") === false || url.startsWith("//")) {
13
+ return url;
14
+ }
15
+
16
+ const path = parsePath(url);
17
+ return createPath({
18
+ ...path,
19
+ pathname: generatePath(path.pathname ?? "/", params),
20
+ });
21
+ };
@@ -1,10 +1,24 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
2
 
3
- import { Links, Meta, Outlet, useMatches } from "react-router";
3
+ import {
4
+ type HeadersFunction,
5
+ Links,
6
+ Meta,
7
+ Outlet,
8
+ useMatches,
9
+ } from "react-router";
4
10
  // @todo think about how to make __generated__ typeable
5
11
  // @ts-ignore
6
12
  import { CustomCode, projectId, lastPublished } from "./__generated__/_index";
7
13
 
14
+ export const headers: HeadersFunction = ({ errorHeaders }) => {
15
+ if (errorHeaders) {
16
+ return errorHeaders;
17
+ }
18
+
19
+ return {};
20
+ };
21
+
8
22
  const Root = () => {
9
23
  // Get language from matches
10
24
  const matches = useMatches();
@@ -1,3 +1,4 @@
1
+ import { type ComponentProps, memo, useMemo } from "react";
1
2
  import {
2
3
  type MetaFunction,
3
4
  type LinksFunction,
@@ -17,6 +18,7 @@ import {
17
18
  formBotFieldName,
18
19
  cachedFetch,
19
20
  } from "@webstudio-is/sdk/runtime";
21
+ import { authenticateRequest } from "@webstudio-is/wsauth";
20
22
  import {
21
23
  ReactSdkContext,
22
24
  PageSettingsMeta,
@@ -24,6 +26,7 @@ import {
24
26
  } from "@webstudio-is/react-sdk/runtime";
25
27
  import {
26
28
  projectId,
29
+ projectDomain,
27
30
  Page,
28
31
  siteName,
29
32
  favIconAsset,
@@ -41,6 +44,25 @@ import * as constants from "__CONSTANTS__";
41
44
  import css from "__CSS__?url";
42
45
  import { sitemap } from "__SITEMAP__";
43
46
  import { assets } from "__ASSETS__";
47
+ import { authRoutes } from "__AUTH__";
48
+
49
+ const authenticateProductionRequest = (request: Request) => {
50
+ const host =
51
+ request.headers.get("x-forwarded-host") ||
52
+ request.headers.get("host") ||
53
+ "";
54
+
55
+ const requestHost = host.split(":")[0];
56
+ if (
57
+ projectDomain !== undefined &&
58
+ (requestHost === projectDomain ||
59
+ requestHost.startsWith(`${projectDomain}.`))
60
+ ) {
61
+ return;
62
+ }
63
+
64
+ return authenticateRequest(request, authRoutes);
65
+ };
44
66
 
45
67
  const customFetch: typeof fetch = (input, init) => {
46
68
  if (typeof input !== "string") {
@@ -82,6 +104,8 @@ const customFetch: typeof fetch = (input, init) => {
82
104
  };
83
105
 
84
106
  export const loader = async (arg: LoaderFunctionArgs) => {
107
+ const authRoute = authenticateProductionRequest(arg.request);
108
+
85
109
  const url = new URL(arg.request.url);
86
110
  const host =
87
111
  arg.request.headers.get("x-forwarded-host") ||
@@ -132,13 +156,18 @@ export const loader = async (arg: LoaderFunctionArgs) => {
132
156
  {
133
157
  status: pageMeta.status,
134
158
  headers: {
135
- "Cache-Control": "public, max-age=600",
159
+ "Cache-Control":
160
+ authRoute === undefined ? "public, max-age=600" : "private, no-store",
136
161
  },
137
162
  }
138
163
  );
139
164
  };
140
165
 
141
- export const headers: HeadersFunction = () => {
166
+ export const headers: HeadersFunction = ({ errorHeaders }) => {
167
+ if (errorHeaders) {
168
+ return errorHeaders;
169
+ }
170
+
142
171
  return {
143
172
  "Cache-Control": "public, max-age=0, must-revalidate",
144
173
  };
@@ -219,6 +248,8 @@ export const action = async ({
219
248
  }: ActionFunctionArgs): Promise<
220
249
  { success: true } | { success: false; errors: string[] }
221
250
  > => {
251
+ authenticateProductionRequest(request);
252
+
222
253
  try {
223
254
  const url = new URL(request.url);
224
255
  url.host = getRequestHost(request);
@@ -297,20 +328,33 @@ export const action = async ({
297
328
  }
298
329
  };
299
330
 
331
+ const PageBoundary = memo(
332
+ ({ url, system }: ComponentProps<typeof Page> & { url: string }) => {
333
+ // Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages
334
+ return <Page key={url} system={system} />;
335
+ },
336
+ // React Router can rerender the current route while the next route loaders are
337
+ // still pending. Keep the generated page out of that pending-navigation render
338
+ // path, but let URL changes remount it.
339
+ (prevProps, nextProps) => prevProps.url === nextProps.url
340
+ );
341
+
300
342
  const Outlet = () => {
301
343
  const { system, resources, url, pageMeta, host } =
302
344
  useLoaderData<typeof loader>();
345
+ const sdkContext = useMemo(
346
+ () => ({
347
+ ...constants,
348
+ resources,
349
+ breakpoints,
350
+ onError: console.error,
351
+ }),
352
+ [resources]
353
+ );
354
+
303
355
  return (
304
- <ReactSdkContext.Provider
305
- value={{
306
- ...constants,
307
- resources,
308
- breakpoints,
309
- onError: console.error,
310
- }}
311
- >
312
- {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
313
- <Page key={url} system={system} />
356
+ <ReactSdkContext.Provider value={sdkContext}>
357
+ <PageBoundary url={url} system={system} />
314
358
  <PageSettingsMeta
315
359
  url={url}
316
360
  pageMeta={pageMeta}
@@ -1,6 +1,7 @@
1
- import { redirect } from "react-router";
1
+ import { type LoaderFunctionArgs, redirect } from "react-router";
2
+ import { generateRedirectUrl } from "../redirect-url";
2
3
  import { url, status } from "__REDIRECT__";
3
4
 
4
- export const loader = () => {
5
- throw redirect(url, status);
5
+ export const loader = (arg: LoaderFunctionArgs) => {
6
+ throw redirect(generateRedirectUrl(url, arg.params), status);
6
7
  };