vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.44k stars 27.04k forks source link

Parallel Routes not working on Cloud Run in Next.js 15 #71626

Open aramikuto opened 1 month ago

aramikuto commented 1 month ago

Link to the code that reproduces this issue

https://github.com/aramikuto/nextjs-parallel-route-server-js-problem

To Reproduce

  1. Build the application and start the standalone server with the following command:
    yarn build && cp -r .next/static .next/standalone/.next/ && node .next/standalone/server.js
  2. Open the network inspector and navigate to the homepage at http://localhost:3000/
  3. Note the URL of the chunk containing the parallel route.

Current vs. Expected behavior

In Next.js 14, the @ symbol in the chunk URL is not encoded, appearing as: http://localhost:3000/_next/static/chunks/app/@modal/page-5d9f089824f6dc9b.js

In Next.js 15.0.0 and 15.0.1-canary.1, the @ symbol is encoded as %40, so the URL looks like: http://localhost:3000/_next/static/chunks/app/%40modal/page-e0360b9b769efb1a.js

When deployed to Cloud Run, this chunk becomes inaccessible because Some encoded URL characters are decoded by Cloud Run. It seems Cloud Run decodes %40 back to @, but the standalone server on Next.js 15 responds with a 404 error for such requests.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Mon Jul 29 21:16:46 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T8112
  Available memory (MB): 24576
  Available CPU cores: 8
Binaries:
  Node: 20.11.1
  npm: 10.2.4
  Yarn: 1.22.21
  pnpm: N/A
Relevant Packages:
  next: 15.0.1-canary.1 // Latest available version is detected (15.0.1-canary.1).
  eslint-config-next: N/A
  react: 19.0.0-rc-69d4b800-20241021
  react-dom: 19.0.0-rc-69d4b800-20241021
  typescript: 5.3.3
Next.js Config:
  output: standalone

Which area(s) are affected? (Select all that apply)

Parallel & Intercepting Routes

Which stage(s) are affected? (Select all that apply)

Other (Deployed)

Additional context

It seems this issue may have been introduced in PR #70256.

nicholas5538 commented 1 month ago

~Same issue here, the parallel route for opening my modal works only on development, but it does not work when I use next start or preview it on the Netlify deploy preview.~

~Code has been left unchanged after the update other than adding the await on my params prop~

My issue has been fixed by removing export const dynamic = 'error';, the solution was to make my parallel routes server-rendered on demand

jeqermigselv-cloud commented 1 month ago

I have the same issue in Azure. Works fine on my local machine, but when deployed to Azure, the files it not found (despite copying it)

Alexandredc commented 1 month ago

@aramikuto did you find any workaround ?

mozisan commented 1 month ago

@Alexandredc Hi, I found a workaround that resolved the issue in my environment. Hope this helps you.

// middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export const config = {
  matcher: "/_next/static/chunks/:path*",
};

export const middleware = (request: NextRequest) => {
  const url = new URL(request.url);

  const decodedPathname = url.pathname.split("/").map(decodeURIComponent).join("/");
  const encodedPathname = decodedPathname.split("/").map(encodeURIComponent).join("/");

  const includesEncodableChar = url.pathname !== encodedPathname;
  if (!includesEncodableChar) return;

  const hasAlreadyDecoded = url.pathname === decodedPathname;
  if (hasAlreadyDecoded) return;

  const destination = new URL(url);
  destination.pathname = encodedPathname;
  return NextResponse.rewrite(destination);
};
Alexandredc commented 1 month ago

@Alexandredc Hi, I found a workaround that resolved the issue in my environment. Hope this helps you.

// middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export const config = {
  matcher: "/_next/static/chunks/:path*",
};

export const middleware = (request: NextRequest) => {
  const url = new URL(request.url);

  const decodedPathname = url.pathname.split("/").map(decodeURIComponent).join("/");
  const encodedPathname = decodedPathname.split("/").map(encodeURIComponent).join("/");

  const includesEncodableChar = url.pathname !== encodedPathname;
  if (!includesEncodableChar) return;

  const hasAlreadyDecoded = url.pathname === decodedPathname;
  if (hasAlreadyDecoded) return;

  const destination = new URL(url);
  destination.pathname = encodedPathname;
  return NextResponse.rewrite(destination);
};

I have tried in Google Cloud Run and it doesn't work :(

Alexandredc commented 1 month ago

@ForsakenHarmony Do you have some guidance to implement a workaround, please? 🙏

aramikuto commented 1 month ago

@Alexandredc I'm currently waiting on a response from Vercel to confirm if they recognize this as an issue. If you need to upgrade right away, it might be worth exploring options for deploying static assets separately; for example, you could use Firebase Hosting to serve static resources. I haven’t checked yet, but hopefully, Firebase Hosting isn’t impacted by the issue.

rollsrobby commented 1 month ago

We have the same running in a Docker container in Azure. It is currently blocking us from upgrading. Unfortunately the middleware workaround doesn't work for us.

Alexandredc commented 3 weeks ago

@aramikuto do you have any news from Vercel ? I tried to find a workaround today, but was unsuccessful. :(

Alexandredc commented 3 weeks ago

Finally, I have found a simple solution that work on Google Cloud Run.

Add a rewrite rule on next.config.ts

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
  rewrites: async () => {
    return {
      beforeFiles: [
        {
          source: '/_next/static/chunks/app/:folder/@breadcrumb/:path*',
          destination: '/_next/static/chunks/app/:folder/%40breadcrumb/:path*'
        }
      ],
      afterFiles: [],
      fallback: []
    };
  }
};

export default nextConfig;
aramikuto commented 3 weeks ago

@Alexandredc unfortunately no news yet. Thank you for sharing the workaround!

ru-van-urk commented 2 weeks ago

For anyone also experiencing this on Google Cloud Run

For me @Alexandredc workaround didn't work because I was using both dynamic routes and route groups. My path looks like this: /_next/static/chunks/app/%5Blocale%5D/admin/(routes)/%40employer/layout-fcdbade18a260943.js

I fixed it by adding a wildcard to the folder slug:

import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin();

/** @type {import("next").NextConfig} */
const config = {
  output: "standalone",
  rewrites: async () => ({
    beforeFiles: [
      {
       // Added wildcard to ':folder' -> ':folder*'
        source: "/_next/static/chunks/app/:folder*/@employer/:path*",
        destination: "/_next/static/chunks/app/:folder*/%40employer/:path*",
      },
      // more parrallel rewrites
    ],
    afterFiles: [],
    fallback: [],
  }),
};

export default withNextIntl(config);