diff --git a/README.md b/README.md index d6edc6a..48e6205 100644 --- a/README.md +++ b/README.md @@ -118,11 +118,12 @@ From now on, whenever you want to preview your application locally, just run: #### Custom Netlify Redirects -You can define custom redirects in the `netlify.toml` file. -Routes defined by `next-on-netlify` take precedence over routes -defined in `netlify.toml`. +You can define custom redirects in a `_redirects` and/or in your `netlify.toml` file. +The precedence of these rules are: -In the past, it was possible to define custom redirects in a `_redirects` file. This is not possible anymore. Let me know if you have a need for this feature and we can add it back. +- `_redirects` +- `next-on-netlify` redirects +- `netlify.toml` [Read more about Netlify redirects here](https://docs.netlify.com/routing/redirects/). diff --git a/lib/config.js b/lib/config.js index 2e711f5..7966064 100644 --- a/lib/config.js +++ b/lib/config.js @@ -23,6 +23,8 @@ const NEXT_DIST_DIR = getNextDistDir({ nextConfigPath: NEXT_CONFIG_PAT // This is the Netlify Function template that wraps all SSR pages const FUNCTION_TEMPLATE_PATH = join(__dirname, "netlifyFunctionTemplate.js") +// This is the file where custom redirects can be configured +const CUSTOM_REDIRECTS_PATH = join(".", "_redirects") module.exports = { NETLIFY_PUBLISH_PATH, @@ -31,4 +33,5 @@ module.exports = { NEXT_CONFIG_PATH, NEXT_DIST_DIR, FUNCTION_TEMPLATE_PATH, + CUSTOM_REDIRECTS_PATH, } diff --git a/lib/setupRedirects.js b/lib/setupRedirects.js index e014cab..0217e46 100644 --- a/lib/setupRedirects.js +++ b/lib/setupRedirects.js @@ -1,9 +1,11 @@ const path = require('path') const { join } = path -const { writeFileSync } = require('fs-extra') +const { existsSync, readFileSync, + writeFileSync } = require('fs-extra') const { default: isDynamicRoute } = require("@sls-next/lambda-at-edge/dist/lib/isDynamicRoute") const { getSortedRoutes } = require("@sls-next/lambda-at-edge/dist/lib/sortedRoutes") -const { NETLIFY_PUBLISH_PATH } = require('./config') +const { NETLIFY_PUBLISH_PATH, + CUSTOM_REDIRECTS_PATH } = require('./config') const allNextJsPages = require('./allNextJsPages') const getNetlifyRoute = require('./getNetlifyRoute') const getNetlifyFunctionName = require('./getNetlifyFunctionName') @@ -30,6 +32,12 @@ const setupRedirects = () => { // Generate redirects as array const redirects = [] + if(existsSync(CUSTOM_REDIRECTS_PATH)) { + console.log(" ", "# Prepending custom redirects") + redirects.push(readFileSync(CUSTOM_REDIRECTS_PATH)) + } + redirects.push("# Next-on-Netlify Redirects") + sortedPages.forEach(page => { // Generate redirect for each page route page.routesAsArray.forEach(route => { @@ -64,7 +72,6 @@ const setupRedirects = () => { // Write redirects to _redirects file writeFileSync( join(NETLIFY_PUBLISH_PATH, "_redirects"), - "# Next-on-Netlify Redirects" + "\n" + redirects.join("\n") ) } diff --git a/tests/customRedirects.test.js b/tests/customRedirects.test.js new file mode 100644 index 0000000..ff0db09 --- /dev/null +++ b/tests/customRedirects.test.js @@ -0,0 +1,84 @@ +// Test next-on-netlify when a custom distDir is set in next.config.js +const { parse, join } = require('path') +const { copySync, emptyDirSync, readFileSync, writeFileSync } = require('fs-extra') +const npmRunBuild = require("./helpers/npmRunBuild") + +// The name of this test file (without extension) +const FILENAME = parse(__filename).name + +// The directory which will be used for testing. +// We simulate a NextJS app within that directory, with pages, and a +// package.json file. +const PROJECT_PATH = join(__dirname, "builds", FILENAME) + +// The directory that contains the fixtures, such as NextJS pages, +// NextJS config, and package.json +const FIXTURE_PATH = join(__dirname, "fixtures") + +// Capture the output of `npm run build` to verify successful build +let BUILD_OUTPUT + +beforeAll( + async () => { + // Clear project directory + emptyDirSync(PROJECT_PATH) + emptyDirSync(join(PROJECT_PATH, "pages")) + + // Copy NextJS pages and config + copySync( + join(FIXTURE_PATH, "pages-with-static-props-index"), + join(PROJECT_PATH, "pages") + ) + copySync( + join(FIXTURE_PATH, "next.config.js"), + join(PROJECT_PATH, "next.config.js") + ) + + // Copy package.json + copySync( + join(FIXTURE_PATH, "package.json"), + join(PROJECT_PATH, "package.json") + ) + + // Create a _redirects file + writeFileSync( + join(PROJECT_PATH, "_redirects"), + "# Custom Redirect Rules\n" + + "https://old.example.com/* https://new.example.com/:splat 301!\n" + ) + + // Invoke `npm run build`: Build Next and run next-on-netlify + const { stdout } = await npmRunBuild({ directory: PROJECT_PATH }) + BUILD_OUTPUT = stdout + }, + // time out after 180 seconds + 180 * 1000 +) + +describe('Next', () => { + test('builds successfully', () => { + // NextJS output + expect(BUILD_OUTPUT).toMatch("Creating an optimized production build...") + expect(BUILD_OUTPUT).toMatch("Automatically optimizing pages...") + expect(BUILD_OUTPUT).toMatch("First Load JS shared by all") + + // Next on Netlify output + expect(BUILD_OUTPUT).toMatch("Next on Netlify") + expect(BUILD_OUTPUT).toMatch("Success! All done!") + }) +}) + +describe('Routing',() => { + test('includes custom redirect rules', async () => { + // Read _redirects file + const contents = readFileSync(join(PROJECT_PATH, "out_publish", "_redirects")) + + const redirects = contents.toString().split(/\n/) + expect(redirects[0]).toEqual("# Custom Redirect Rules") + expect(redirects[1]).toEqual("https://old.example.com/* https://new.example.com/:splat 301!") + + // Check that other routes are present + expect(redirects).toContain("/ /index.html 200") + expect(redirects).toContain("/index /index.html 200") + }) +})