netlify/next-runtime

[Bug]: next.config.js > redirects is not respected

dragons-library opened this issue ยท 8 comments

Summary

Redirects defined via next.config.js#redirect are note respected when deployed to netlify using @netlify/plugin-next.

This happens in a Next.js v14 app using traditional pages/ (I have not tested with app/ directory routing). This also happens on Next.js v13 and has been an issue for some time. I know this used to work at some point, but it has been some time since it did work.

I suspect that generateRedirects should probably do something with routes-manifest.json#redirects.

Reference reports:

A link to a reproduction repository

https://github.com/dragons-library/next-netlify-redirect-issue

Expected Result

https://next-netlify-redirect-issue.netlify.app/next-redirect should redirect to https://next-netlify-redirect-issue.netlify.app/redirect-success

Actual Result

Does not redirect and 404s when deployed to netlify.

Steps to reproduce

Redirect failing on Netlify

  1. visit https://next-netlify-redirect-issue.netlify.app
  2. click either link
  3. Netlify Redirect navigates to /netlify-redirect (defined in netlify.toml and works as expected)
  4. Next Redirect navigates to /next-redirect (defined in next.config.js and does NOT work as expected)

Redirect working locally

  1. then, checkout the reproduction repo locally and test...
  2. git clone https://github.com/dragons-library/next-netlify-redirect-issue.git
  3. cd next-netlify-redirect-issue
  4. npm install
  5. npm run dev
  6. visit http://localhost:3000/next-redirect
  7. if you deploy locally via netlify cli, the /next-redirect stops working locally

Next Runtime version

4.41.1

Is your issue related to the app directory?

  • Yes, I am using the app directory

More information about your build

  • I am building using the CLI
  • I am building using file-based configuration (netlify.toml)

What OS are you using?

None

Your netlify.toml file

`netlify.toml`
[build]
  command = "npm run build"
  publish = ".next"

[[plugins]]
  package = "netlify-plugin-cypress"

[[redirects]]
  from = "/netlify-redirect"
  to = "/redirect-success"

Your public/_redirects file

`_redirects`
# Paste content of your `_redirects` file here

Your next.config.js file

`next.config.js`
module.exports = {
  async redirects() {
    return [
      {
        source: '/next-redirect',
        destination: '/redirect-success',
        permanent: false,
      },
    ];
  },
};

Builds logs (or link to your logs)

Build logs
9:32:38 PM: Netlify configuration property "redirects" value changed to [
9:32:38 PM:   {
9:32:38 PM:     from: "/netlify-redirect",
9:32:38 PM:     query: {},
9:32:38 PM:     to: "/redirect-success",
9:32:38 PM:     force: false,
9:32:38 PM:     conditions: {},
9:32:38 PM:     headers: {}
9:32:38 PM:   },
9:32:38 PM:   { from: "/_next/static/*", to: "/static/:splat", status: 200 },
9:32:38 PM:   {
9:32:38 PM:     from: "/_next/image*",
9:32:38 PM:     query: { url: ":url", w: ":width", q: ":quality" },
9:32:38 PM:     to: "/_ipx/w_:width,q_:quality/:url",
9:32:38 PM:     status: 301
9:32:38 PM:   },
9:32:38 PM:   { from: "/_ipx/*", to: "/.netlify/builders/_ipx", status: 200 },
9:32:38 PM:   {
9:32:38 PM:     from: "/api/*",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/favicon.ico",
9:32:38 PM:     to: "/favicon.ico",
9:32:38 PM:     conditions: { Cookie: [Array] },
9:32:38 PM:     status: 200
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/logo-netlify.svg",
9:32:38 PM:     to: "/logo-netlify.svg",
9:32:38 PM:     conditions: { Cookie: [Array] },
9:32:38 PM:     status: 200
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/*",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200,
9:32:38 PM:     conditions: { Cookie: [Array] },
9:32:38 PM:     force: true
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/_next/data/_UlOizIbfHuw31IzA8GOY/index.json",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200,
9:32:38 PM:     force: false
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200,
9:32:38 PM:     force: false
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/_next/data/_UlOizIbfHuw31IzA8GOY/redirect-success.json",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200,
9:32:38 PM:     force: false
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/redirect-success",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200,
9:32:38 PM:     force: false
9:32:38 PM:   },
9:32:38 PM:   {
9:32:38 PM:     from: "/*",
9:32:38 PM:     to: "/.netlify/functions/___netlify-handler",
9:32:38 PM:     status: 200
9:32:38 PM:   }
9:32:38 PM: ].

Function logs

Function logs
# Paste logs here

.next JSON files

generated .next JSON files

.next/routes-manifest.json

{
  "version": 3,
  "pages404": true,
  "caseSensitive": false,
  "basePath": "",
  "redirects": [
    {
      "source": "/:path+/",
      "destination": "/:path+",
      "internal": true,
      "statusCode": 308,
      "regex": "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$"
    },
    {
      "source": "/next-redirect",
      "destination": "/redirect-success",
      "statusCode": 307,
      "regex": "^(?!/_next)/next-redirect(?:/)?$"
    }
  ],
  "headers": [],
  "dynamicRoutes": [],
  "staticRoutes": [
    {
      "page": "/",
      "regex": "^/(?:/)?$",
      "routeKeys": {},
      "namedRegex": "^/(?:/)?$"
    },
    {
      "page": "/redirect-success",
      "regex": "^/redirect\\-success(?:/)?$",
      "routeKeys": {},
      "namedRegex": "^/redirect\\-success(?:/)?$"
    }
  ],
  "dataRoutes": [],
  "rsc": {
    "header": "RSC",
    "varyHeader": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url",
    "prefetchHeader": "Next-Router-Prefetch",
    "contentTypeHeader": "text/x-component"
  },
  "rewrites": []
}

This rough patch resolves my immediate use case (simple redirects), verified via patch-package and deployed to netlify. Probably needs to be fleshed out more to handle all of the NextJS redirect features (has, missing, etc).

patches/@netlify+plugin-nextjs+4.41.1.patch

diff --git a/node_modules/@netlify/plugin-nextjs/lib/helpers/redirects.js b/node_modules/@netlify/plugin-nextjs/lib/helpers/redirects.js
index 7002f41..8577698 100644
--- a/node_modules/@netlify/plugin-nextjs/lib/helpers/redirects.js
+++ b/node_modules/@netlify/plugin-nextjs/lib/helpers/redirects.js
@@ -180,7 +180,35 @@ const generateDynamicRewrites = ({ dynamicRoutes, prerenderedDynamicRoutes, midd
 exports.generateDynamicRewrites = generateDynamicRewrites;
 const generateRedirects = async ({ netlifyConfig, nextConfig: { i18n, basePath, trailingSlash, appDir }, buildId, apiLambdas, }) => {
     const { dynamicRoutes: prerenderedDynamicRoutes, routes: prerenderedStaticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'prerender-manifest.json'));
-    const { dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
+    const { redirects, dynamicRoutes, staticRoutes } = await (0, fs_extra_1.readJSON)((0, pathe_1.join)(netlifyConfig.build.publish, 'routes-manifest.json'));
+
+    netlifyConfig.redirects.push(...redirects.reduce((redirects, redirect) => {
+        const { source: route, destination: to, statusCode: status, has, missing, internal } = redirect;
+        // TODO: confirm we should skip all `internal` routes
+        if (internal) {
+            return redirects;
+        }
+
+        // TODO: support missing / has
+        if (missing || has) {
+            console.warn('[skipping unsupported NextJS redirect]', redirect);
+            return redirects;
+        }
+
+        return [
+            ...redirects,
+            ...(0, utils_1.redirectsForNextRoute)({
+                route,
+                basePath,
+                to,
+                status,
+                buildId,
+                force: true,
+                i18n: null,
+            }),
+        ];
+    }, []));
+
     if (i18n && i18n.localeDetection !== false) {
         netlifyConfig.redirects.push(...generateLocaleRedirects({ i18n, basePath, trailingSlash }));
     }

bumping this, I need Next.js rewrites in order to redirect my API routes. Have used the patch in the time being but would much prefer this resolved in the package.

bumping this, I need Next.js rewrites in order to redirect my API routes. Have used the patch in the time being but would much prefer this resolved in the package.

I had this issue recently, I was told by support that Netlify is currently working to resolve known issues for their nextjs 13/14 runtimes, and was asked to use a _redirects or netlify.toml file instead.

bumping this, I need Next.js rewrites in order to redirect my API routes. Have used the patch in the time being but would much prefer this resolved in the package.

I had this issue recently, I was told by support that Netlify is currently working to resolve known issues for their nextjs 13/14 runtimes, and was asked to use a _redirects or netlify.toml file instead.

I tried both _redirects and a normally use a netlify.toml, but I think that API routes can't be mapped with these / I couldn't figure out how to get them to work with it.

Hi, I'm dealing with the same problem. Any solution?

When I was using Umami, I also encountered this issue. I hope Netlify can fix this issue

umami-software/umami#2411

I think this is resolved with v5 of the plugin. I updated @netlify/plugin-nextjs to 5.0.0 and removed my workaround and all my redirects seem to be working as expected, including ones that previously didn't work.

If you're on Next.js >= 13.5 and node >= 18, give the v5 plugin a try and it should hopefully resolve your issues.

Fixed in v5.