Skip to content

Commit

Permalink
Fix support for trailing / in cloudflare-pages
Browse files Browse the repository at this point in the history
  • Loading branch information
tadasbtl committed Jun 2, 2022
1 parent 065c4f4 commit 6ed7456
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 31 deletions.
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@ configuration for those third party hosting providers.
npm install nexered
```
2. Update your build script in `package.json`
```diff
{
"scripts": {
- "build": "next build && next export"
+ "build": "next build && next export && nexered --provider=cloudflare-pages"
}
}
```
2. Add `nexered` to the `postbuild` [(or the `post-` script of the static export script if it's named differently)](https://docs.npmjs.com/cli/v6/using-npm/scripts#pre--post-scripts) step in `package.json`
```diff
{
"scripts": {
"postexport": "nexered --provider=cloudflare-pages"
}
}
```

3. ...

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "nexered",
"version": "0.1.2",
"version": "0.1.3",
"description": "Next.js config redirect export for cloudflare",
"repository": "github:varanauskas/nexered",
"author": "Tadas Varanauskas <[email protected]>",
"license": "MIT",
"private": false,
"scripts": {
"test": "jest",
"build": "tsc",
"cleanup": "rm -rf ./dist",
"prepack": "npm run cleanup && npm run build"
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import yargs from "yargs";
import { main, providers } from ".";

export const { argv } = yargs
const { argv } = yargs
.usage('$0 --p=cloudflare-pages')
.options({
'p': { choices: Object.keys(providers) as (keyof typeof providers)[], demandOption: true, alias: 'provider', description: 'hosting provider type' },
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import loadConfig from "next/dist/server/config";
import { PHASE_EXPORT } from "next/dist/shared/lib/constants";
import { argv } from "./cli";
import * as CloudflareProvider from "./providers/cloudflare-pages";

export const providers = {
Expand All @@ -9,5 +8,5 @@ export const providers = {

export async function main(provider: keyof typeof providers, nextDir: string, outputDir: string) {
const config = await loadConfig(PHASE_EXPORT, nextDir);
providers[provider].addRedirects(config, { nextDir, outputDir });
return providers[provider].addRedirects(config, { nextDir, outputDir });
}
20 changes: 12 additions & 8 deletions src/providers/cloudflare-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ import { writeFile, mkdir } from "fs/promises";
import { join } from "path";

type Paths = { source: string, destination: string };
function cloudflarePaths(paths: Paths): Paths {
function cloudflarePaths(paths: Paths): Paths[] {
const { source, destination } = paths;
const wildcardMatch = source.match(/(:[^\/]+\*)$/);
if (!wildcardMatch || wildcardMatch.length === 0) return paths;
// Adding another redirect with trailing `/`
// as Cloudflare Pages uses explicit trailing `/`
// while Next.js ignores them when routing
if (!wildcardMatch || wildcardMatch.length === 0)
return [paths, { source: `${source}/`, destination}];
const [wildcard] = wildcardMatch;
return {
return [{
source: source.replace(wildcard, '*'),
destination: destination.replace(wildcard, ':splat')
};
}];
}

// https://developers.cloudflare.com/pages/platform/redirects/
function cloudflareRedirect({
permanent,
statusCode = permanent ? PERMANENT_REDIRECT_STATUS : TEMPORARY_REDIRECT_STATUS,
...redirect }: NextRedirect): string {
const { source, destination } = cloudflarePaths(redirect);
return `${source} ${destination} ${statusCode}`;
...redirect }: NextRedirect): string[] {
const paths = cloudflarePaths(redirect);
return paths.map(({ source, destination }) => `${source} ${destination} ${statusCode}`);
}

const cloudflareRedirects = (redirects: NextRedirect[]): string => redirects
.map(cloudflareRedirect)
.flatMap(cloudflareRedirect)
.join('\n');

export const addRedirects: AddProviderRedirects = async (config, { outputDir }) => {
Expand Down
25 changes: 16 additions & 9 deletions test/cloudflare-pages/cloudflare-pages.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,48 @@
import { existsSync } from "fs";
import { readFile } from "fs/promises";
import { rm } from "fs/promises";
import { readFile, rm } from "fs/promises";
import { mkdtemp } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { main } from "../../src";

const EXPECTED = `/base/about /base 308
/base/about/ /base 308
/base/old-blog/* /base/blog/:splat 307
/base/old-blog/:slug /base/news/:slug 308
/base/old-blog/:slug/ /base/news/:slug 308
/base/blog/* /base/news/:splat 308
/base/with-basePath /base/another 307
/base/with-basePath/ /base/another 307
/without-basePath /another 307
/base/custom-status-code /base/status-code-303 303`;
/without-basePath/ /another 307
/base/custom-status-code /base/status-code-303 303
/base/custom-status-code/ /base/status-code-303 303`;

describe("cloudflare provider", () => {
let __NEXT_TEST_MODE: string | undefined;
let outputDir: string;

beforeAll(() => {
__NEXT_TEST_MODE = process.env.__NEXT_TEST_MODE;
process.env.__NEXT_TEST_MODE = 'jest';
});

afterAll(() => process.env.__NEXT_TEST_MODE = __NEXT_TEST_MODE);
beforeEach(async () => { outputDir = await mkdtemp(join(tmpdir(), 'nexered-cloudflare-pages-test-')) });

afterEach(() => rm(outputDir, { recursive: true, force: true }));

afterAll(() => { process.env.__NEXT_TEST_MODE = __NEXT_TEST_MODE; });

it("correctly populates _redirects file with redirects", async () => {
const nextDir = join(__dirname, '/with-redirects');
const outputDir = join(nextDir, '/out');
await main('cloudflare-pages', nextDir, outputDir);
const data = await readFile(join(outputDir, '/_redirects'), "ascii");
expect(data).toEqual(EXPECTED);
await rm(outputDir, { recursive: true, force: true });
});

it("does not create redirects if not necessary", async () => {
const nextDir = join(__dirname, '/empty');
const outputDir = join(nextDir, '/out');
await main('cloudflare-pages', nextDir, outputDir);
expect(await existsSync(join(outputDir, '/_redirects'))).toBe(false);
await rm(outputDir, { recursive: true, force: true });
expect(existsSync(join(outputDir, '/_redirects'))).toBe(false);
});
});

0 comments on commit 6ed7456

Please sign in to comment.