Skip to content

Commit

Permalink
fix: next.config.js URL rewrites unintentionally redirecting instea…
Browse files Browse the repository at this point in the history
…d of rewriting (#394)

* fix: `next.config.js` rewrites unintentionally redirecting instead of rewriting

* fix fetch not using original method

* mock fetch
  • Loading branch information
james-elicx authored Jul 24, 2023
1 parent ee6b7dc commit f08f2b9
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-carrots-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudflare/next-on-pages': patch
---

Fix `next.config.js` rewrites unintentionally redirecting the user instead of rewriting the request.
29 changes: 18 additions & 11 deletions packages/next-on-pages/templates/_worker.js/handleRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MatchedSet } from './utils';
import { applyHeaders, runOrFetchBuildOutputItem } from './utils';
import { applyHeaders, isUrl, runOrFetchBuildOutputItem } from './utils';
import { RoutesMatcher } from './routes-matcher';
import type { RequestContext } from '../../src/utils/requestContext';

Expand Down Expand Up @@ -86,16 +86,23 @@ async function generateResponse(
return new Response(null, { status, headers: headers.normal });
}

let resp =
body !== undefined
? // If we have a response body from matching, use it instead.
new Response(body, { status })
: await runOrFetchBuildOutputItem(output[path], reqCtx, {
path,
status,
headers,
searchParams,
});
let resp: Response;

if (body !== undefined) {
// If we have a response body from matching, use it instead.
resp = new Response(body, { status });
} else if (isUrl(path)) {
// If the path is an URL from matching, that means it was rewritten to a full URL.
resp = await fetch(new URL(path), reqCtx.request);
} else {
// Otherwise, we need to serve a file from the Vercel build output.
resp = await runOrFetchBuildOutputItem(output[path], reqCtx, {
path,
status,
headers,
searchParams,
});
}

const newHeaders = headers.normal;
applyHeaders(newHeaders, resp.headers);
Expand Down
5 changes: 0 additions & 5 deletions packages/next-on-pages/templates/_worker.js/routes-matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,6 @@ export class RoutesMatcher {
this.checkPhaseCounter = 0;
const result = await this.checkPhase(phase);

// Check if path is an external URL.
if (isUrl(this.path)) {
this.headers.normal.set('location', this.path);
}

// Update status to redirect user to external URL.
if (
this.headers.normal.has('location') &&
Expand Down
3 changes: 2 additions & 1 deletion packages/next-on-pages/tests/_helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export type TestCase = {
method?: string;
expected: {
status: number;
data: string;
data: string | RegExp;
headers?: Record<string, string>;
ignoreHeaders?: boolean;
reqHeaders?: Record<string, string>;
mockConsole?: { log?: string[]; error?: (string | Error)[] };
};
Expand Down
32 changes: 27 additions & 5 deletions packages/next-on-pages/tests/templates/handleRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,17 @@ function runTestCase(
);

expect(res.status).toEqual(expected.status);
await expect(res.text()).resolves.toEqual(expected.data);
expect(Object.fromEntries(res.headers.entries())).toEqual(
expected.headers || {},
);
const textContent = await res.text();
if (expected.data instanceof RegExp) {
expect(textContent).toMatch(expected.data);
} else {
expect(textContent).toEqual(expected.data);
}
if (!expected.ignoreHeaders) {
expect(Object.fromEntries(res.headers.entries())).toEqual(
expected.headers || {},
);
}
if (expected.reqHeaders) {
expect(Object.fromEntries(req.headers.entries())).toEqual(
expected.reqHeaders,
Expand All @@ -87,6 +94,18 @@ function runTestCase(
* @param testSet Test set to run.
*/
async function runTestSet({ config, files, testCases }: TestSet) {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (...args: Parameters<typeof originalFetch>) => {
const req = new Request(...args);
const url = new URL(req.url);

if (url.hostname === 'external-test-url.com') {
return new Response('external test url response');
}

return originalFetch(...args);
};

const { vercelConfig, buildOutput, assetsFetcher, restoreMocks } =
await createRouterTestData(config, files);

Expand All @@ -99,7 +118,10 @@ async function runTestSet({ config, files, testCases }: TestSet) {
runTestCase(reqCtx, vercelConfig, buildOutput, testCase),
);

afterAll(() => restoreMocks());
afterAll(() => {
globalThis.fetch = originalFetch;
restoreMocks();
});
}

vi.mock('esbuild', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const rawVercelConfig: VercelConfig = {
{ handle: 'resource' },
{
src: '^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$',
dest: 'https://my-old-site.com/$1',
dest: 'https://external-test-url.com/$1',
check: true,
},
{ src: '/.*', status: 404 },
Expand Down Expand Up @@ -165,11 +165,13 @@ export const testSet: TestSet = {
},
{
name: 'rewrites - `fallback`: rewrites on any request that has not been matched',
paths: ['/invalid-route'],
paths: ['/plain'],
expected: {
status: 307,
data: '',
headers: { location: 'https://my-old-site.com/invalid-route' },
status: 200,
data: 'external test url response',
headers: {
'content-type': 'text/plain;charset=UTF-8',
},
},
},
{
Expand Down

0 comments on commit f08f2b9

Please sign in to comment.