diff --git a/packages/website-router/package.json b/packages/website-router/package.json
index 3fd32f99d..adccf3cd3 100644
--- a/packages/website-router/package.json
+++ b/packages/website-router/package.json
@@ -4,6 +4,7 @@
"main": "index.ts",
"scripts": {
"dev": "wrangler dev",
+ "test": "node --import @oxc-node/core/register --test \"src/**/*.test.ts\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
@@ -11,6 +12,7 @@
},
"devDependencies": {
"@cloudflare/workers-types": "4.20250321.0",
+ "@oxc-node/core": "^0.0.35",
"@types/node": "20.17.12",
"typescript": "5.7.3",
"wrangler": "3.78.7"
diff --git a/packages/website-router/src/config.ts b/packages/website-router/src/config.ts
index c6de83bda..764f43288 100644
--- a/packages/website-router/src/config.ts
+++ b/packages/website-router/src/config.ts
@@ -1,5 +1,6 @@
export type RewriteRecord = {
rewrite: string;
+ preserveSearch?: boolean;
crisp?: { segments: string[] };
sitemap?: boolean;
banner?: string;
@@ -102,6 +103,7 @@ export const jsonConfig = {
},
'/graphql/hive-testing': {
rewrite: 'hive-platform-docs.theguild.workers.dev',
+ preserveSearch: true,
crisp: { segments: ['hive-website'] },
sitemap: false,
},
diff --git a/packages/website-router/src/routing.test.ts b/packages/website-router/src/routing.test.ts
new file mode 100644
index 000000000..c2ca19b0e
--- /dev/null
+++ b/packages/website-router/src/routing.test.ts
@@ -0,0 +1,37 @@
+///
+
+import { equal } from 'node:assert/strict';
+import { test } from 'node:test';
+import { buildUpstreamUrl } from './routing';
+
+test('preserveSearch keeps the original query string', () => {
+ const upstreamUrl = buildUpstreamUrl({
+ request: new Request(
+ 'https://the-guild.dev/graphql/hive-testing/_serverFn/test?payload=%7B%22foo%22%3A1%7D',
+ ),
+ record: {
+ preserveSearch: true,
+ rewrite: 'hive-platform-docs.theguild.workers.dev',
+ },
+ upstreamPath: '/_serverFn/test',
+ });
+
+ equal(
+ upstreamUrl.toString(),
+ 'https://hive-platform-docs.theguild.workers.dev/_serverFn/test?payload=%7B%22foo%22%3A1%7D',
+ );
+});
+
+test('default rewrite behavior still drops search params', () => {
+ const upstreamUrl = buildUpstreamUrl({
+ request: new Request(
+ 'https://the-guild.dev/graphql/hive-testing/_serverFn/test?payload=%7B%22foo%22%3A1%7D',
+ ),
+ record: {
+ rewrite: 'hive-platform-docs.theguild.workers.dev',
+ },
+ upstreamPath: '/_serverFn/test',
+ });
+
+ equal(upstreamUrl.toString(), 'https://hive-platform-docs.theguild.workers.dev/_serverFn/test');
+});
diff --git a/packages/website-router/src/routing.ts b/packages/website-router/src/routing.ts
index c6eaae6a7..b06319a2b 100644
--- a/packages/website-router/src/routing.ts
+++ b/packages/website-router/src/routing.ts
@@ -102,6 +102,20 @@ export function redirect(sentry: Toucan, from: string, url: string, code = 301)
export type ManipulateResponseFn = (record: RewriteRecord, response: Response) => Promise;
+export function buildUpstreamUrl(options: {
+ request: Request;
+ record: RewriteRecord;
+ upstreamPath: string;
+}) {
+ const upstreamUrl = new URL(`https://${options.record.rewrite}${options.upstreamPath || ''}`);
+
+ if (options.record.preserveSearch) {
+ upstreamUrl.search = new URL(options.request.url).search;
+ }
+
+ return upstreamUrl;
+}
+
export async function handleRewrite(options: {
request: Request;
cacheStorageId: number;
@@ -114,7 +128,7 @@ export async function handleRewrite(options: {
match: string | null;
publicDomain: string;
}): Promise {
- const url = `https://${options.record.rewrite}${options.upstreamPath || ''}`;
+ const url = buildUpstreamUrl(options).toString();
const cacheKey = new Request(url, options.request);
const cache = await caches.open(String(options.cacheStorageId));
let response = await cache.match(cacheKey);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c7bd3b699..2c0d4efb3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -229,6 +229,9 @@ importers:
'@cloudflare/workers-types':
specifier: 4.20250321.0
version: 4.20250321.0
+ '@oxc-node/core':
+ specifier: ^0.0.35
+ version: 0.0.35
'@types/node':
specifier: 20.17.12
version: 20.17.12
@@ -524,12 +527,21 @@ packages:
'@emnapi/core@1.3.1':
resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==}
+ '@emnapi/core@1.8.1':
+ resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
+
'@emnapi/runtime@1.3.1':
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
'@emnapi/wasi-threads@1.0.1':
resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==}
+ '@emnapi/wasi-threads@1.1.0':
+ resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+
'@emotion/is-prop-valid@0.8.8':
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
@@ -1280,6 +1292,9 @@ packages:
'@napi-rs/wasm-runtime@0.2.7':
resolution: {integrity: sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==}
+ '@napi-rs/wasm-runtime@1.1.1':
+ resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
+
'@next/bundle-analyzer@14.2.5':
resolution: {integrity: sha512-BtBbI8VUnB7s4m9ut6CkeJ8Hyx+aq+86mbH+uAld7ZGG0/eH4+5hcPnkHKsQM/yj74iClazS0fninI8yZbIZWA==}
@@ -1383,6 +1398,94 @@ packages:
resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==}
engines: {node: ^16.14.0 || >=18.0.0}
+ '@oxc-node/core-android-arm-eabi@0.0.35':
+ resolution: {integrity: sha512-Vgw/DtArB1fZJ7LX4FX7YDpRvWzwv80lFNngTcamS+9Kbd83HpZb6Tg38t6f7Ubyc/+cL0TTMo55Kg8gwnQGHQ==}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-node/core-android-arm64@0.0.35':
+ resolution: {integrity: sha512-Z/2jKqkTybSDnx2lBb44K0TLD2eUgLMi0te0pp5p5GVnsOZ8A+qSnhZpsPAR8GAbGERdMNOWrDYqj0/VYQt7sQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-node/core-darwin-arm64@0.0.35':
+ resolution: {integrity: sha512-aeEG/a1zj8pA6GC0P7NypzdDY0c6AbZOdbxaGl9UQlwGgHmw6sOtq5PJO+7rCvzxUKPxBH9VZOVg0laFcncIFw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-node/core-darwin-x64@0.0.35':
+ resolution: {integrity: sha512-MtxGaUR2LBcUmqINyxzSYdx5om9KlFjyvN8cx/NizH6U5nYs7Wh/XAIoGpcQmkK2snT1FsgJeGR9L01Q1oqmng==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-node/core-freebsd-x64@0.0.35':
+ resolution: {integrity: sha512-xsZNqMeavNxi/WTxaQMZj6walhj7skCu36yff5q0RjsV7Dzp3RQ/SYK1t3ydw3cwPz2oZCx0AukAYJAj4i9vdw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-node/core-linux-arm-gnueabihf@0.0.35':
+ resolution: {integrity: sha512-F+d948mEzQMvcv0BQO3NN4DP0jsJwvvD5VGCFcR2mFa/z6L7UuRkS8yOKnP7tUfZjri7BwxXn37Szj0ZY7pEPA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-node/core-linux-arm64-gnu@0.0.35':
+ resolution: {integrity: sha512-wKbAstp6Ztq5UVBf/csnMTPMef+wGsrNykJCAtJuO/l88Okm4jn6wnhudeD3hf/426Vw93txBS8veqN2JSb6fg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-node/core-linux-arm64-musl@0.0.35':
+ resolution: {integrity: sha512-IdLaYnFrDGRICQ86AoEQEv5Rfo//knhg4g9ABy7QE3C231C3YpbgwtY7YH7Qv+xHDuUHnTNo8Lo/iraSIY3tQA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-node/core-linux-ppc64-gnu@0.0.35':
+ resolution: {integrity: sha512-yxfWpG2as+al6G9epDvFk8AX1UWy76WlwCP3pUGtpEUGuoAO63JAHUMDSXv1sSd1YatJmRJ75ptexU6c/xMdXg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxc-node/core-linux-s390x-gnu@0.0.35':
+ resolution: {integrity: sha512-nH3mnP6ger1i4LaroWhvtk3coquNYBJD9eqG3OEuJEFGo1Ao80irFcFoktQCLLq47uomYuNQxNJw5covYNHvLw==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-node/core-linux-x64-gnu@0.0.35':
+ resolution: {integrity: sha512-2VKErkkTxLViK/8xbdRoQ9+sid8ZGRROLkcmMtrggjQLU69EhL0wioUVztnDVjHfOPAN17lEAN7tUgxz+PAxCg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-node/core-linux-x64-musl@0.0.35':
+ resolution: {integrity: sha512-QDDZYWMbwB/1uyn0BPMYeqT6miWQBljzLCYESmsVcaHOps204yKHI1Ezp79n2BiYEghhu9RPWrOd4wZ7+Gqa7Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-node/core-openharmony-arm64@0.0.35':
+ resolution: {integrity: sha512-ihb0W8mc0iM9SpfFwj9xY/1gVAPv2y7fGuW2w4jWOICCY2enJ8GnY2N9eYloPkHd2/2+S87M63H998psVZQquQ==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxc-node/core-wasm32-wasi@0.0.35':
+ resolution: {integrity: sha512-GoT1X1Rw3MXbvU25rsqT6gLhl9AKBdLe1ss6pVHxzps0Va6qrSD/2H4alGglUX+qccKcw0kCgJbPKJphM/0CrQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-node/core-win32-arm64-msvc@0.0.35':
+ resolution: {integrity: sha512-ObSjUyRd5md+hKg4j8ufhjaeXHGm4f+9cz1y20mOHr/HOkBIY6CNoPM7x5JEzZNerVZ9Ye62G6t6HNQZttBjsg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-node/core-win32-ia32-msvc@0.0.35':
+ resolution: {integrity: sha512-ZE7/di30tfhh/2ItgcZim4aPLw1ve+TQrp6oJSqMRyYjq0k1AwFrxIqICbaAG9sk79ap9Sy1icFMfFgSkhnABQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-node/core-win32-x64-msvc@0.0.35':
+ resolution: {integrity: sha512-9OyyjY/ECi1icwq32baG0Uct7RuAHbVxzGDffJzNhRtBABpQiIQauoaVuYiSlNecqnA8qFYxh2wxbKaVlsR1YA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@oxc-node/core@0.0.35':
+ resolution: {integrity: sha512-PV46QRDI2wCDdaPzppEh4UfzFmmpSt+1dX32ooq8RWb0BuWX24+LKYicAmSrsk1ls8JRSkAqiWrjrYFHIGozGg==}
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -1800,6 +1903,9 @@ packages:
postcss-lightningcss: ^1.0.1
tailwindcss: ^3.4.14
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
'@tybys/wasm-util@0.9.0':
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
@@ -2981,6 +3087,7 @@ packages:
eslint-plugin-markdown@3.0.1:
resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ deprecated: Please use @eslint/markdown instead
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -3337,11 +3444,12 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
@@ -4012,6 +4120,7 @@ packages:
mathjax-full@3.2.2:
resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==}
+ deprecated: Version 4 replaces this package with the scoped package @mathjax/src
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
@@ -4448,6 +4557,7 @@ packages:
next@14.2.5:
resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==}
engines: {node: '>=18.17.0'}
+ deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
@@ -4726,6 +4836,10 @@ packages:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -6457,16 +6571,32 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@emnapi/core@1.8.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.1.0
+ tslib: 2.8.1
+ optional: true
+
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.8.1
optional: true
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@emnapi/wasi-threads@1.0.1':
dependencies:
tslib: 2.8.1
optional: true
+ '@emnapi/wasi-threads@1.1.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@emotion/is-prop-valid@0.8.8':
dependencies:
'@emotion/memoize': 0.7.4
@@ -6993,6 +7123,13 @@ snapshots:
'@tybys/wasm-util': 0.9.0
optional: true
+ '@napi-rs/wasm-runtime@1.1.1':
+ dependencies:
+ '@emnapi/core': 1.8.1
+ '@emnapi/runtime': 1.8.1
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
'@next/bundle-analyzer@14.2.5':
dependencies:
webpack-bundle-analyzer: 4.10.1
@@ -7102,6 +7239,81 @@ snapshots:
dependencies:
which: 4.0.0
+ '@oxc-node/core-android-arm-eabi@0.0.35':
+ optional: true
+
+ '@oxc-node/core-android-arm64@0.0.35':
+ optional: true
+
+ '@oxc-node/core-darwin-arm64@0.0.35':
+ optional: true
+
+ '@oxc-node/core-darwin-x64@0.0.35':
+ optional: true
+
+ '@oxc-node/core-freebsd-x64@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-arm-gnueabihf@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-arm64-gnu@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-arm64-musl@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-ppc64-gnu@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-s390x-gnu@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-x64-gnu@0.0.35':
+ optional: true
+
+ '@oxc-node/core-linux-x64-musl@0.0.35':
+ optional: true
+
+ '@oxc-node/core-openharmony-arm64@0.0.35':
+ optional: true
+
+ '@oxc-node/core-wasm32-wasi@0.0.35':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.1.1
+ optional: true
+
+ '@oxc-node/core-win32-arm64-msvc@0.0.35':
+ optional: true
+
+ '@oxc-node/core-win32-ia32-msvc@0.0.35':
+ optional: true
+
+ '@oxc-node/core-win32-x64-msvc@0.0.35':
+ optional: true
+
+ '@oxc-node/core@0.0.35':
+ dependencies:
+ pirates: 4.0.7
+ optionalDependencies:
+ '@oxc-node/core-android-arm-eabi': 0.0.35
+ '@oxc-node/core-android-arm64': 0.0.35
+ '@oxc-node/core-darwin-arm64': 0.0.35
+ '@oxc-node/core-darwin-x64': 0.0.35
+ '@oxc-node/core-freebsd-x64': 0.0.35
+ '@oxc-node/core-linux-arm-gnueabihf': 0.0.35
+ '@oxc-node/core-linux-arm64-gnu': 0.0.35
+ '@oxc-node/core-linux-arm64-musl': 0.0.35
+ '@oxc-node/core-linux-ppc64-gnu': 0.0.35
+ '@oxc-node/core-linux-s390x-gnu': 0.0.35
+ '@oxc-node/core-linux-x64-gnu': 0.0.35
+ '@oxc-node/core-linux-x64-musl': 0.0.35
+ '@oxc-node/core-openharmony-arm64': 0.0.35
+ '@oxc-node/core-wasm32-wasi': 0.0.35
+ '@oxc-node/core-win32-arm64-msvc': 0.0.35
+ '@oxc-node/core-win32-ia32-msvc': 0.0.35
+ '@oxc-node/core-win32-x64-msvc': 0.0.35
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -7502,6 +7714,11 @@ snapshots:
postcss-lightningcss: 1.0.2(postcss@8.4.49)
tailwindcss: 3.4.17
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@tybys/wasm-util@0.9.0':
dependencies:
tslib: 2.8.1
@@ -11431,6 +11648,8 @@ snapshots:
pirates@4.0.6: {}
+ pirates@4.0.7: {}
+
pluralize@8.0.0: {}
possible-typed-array-names@1.0.0: {}