next.js: next.js 14 redirect() inside a server action can't switch between root layouts (was working in next.js 13)

Link to the code that reproduces this issue

https://github.com/meszaros-lajos-gyorgy/nextjs-14-redirect-bug

To Reproduce

  1. start the development server with npm run dev
  2. open http://localhost:3000/en in your browser
  3. Click on the “search” submit button on top of the page next to the text input (it doesn’t matter what you enter into the textfield, it always redirects to the same route -> http://localhost:3000/en/search/hello)

Current vs. Expected behavior

Expected result:

You should land on /en/search/hello as that is the hardcoded redirection inside the server action in services/SiteSearch.service.ts.

( This was the behavior in Next.js 13.4.12, but it also threw an error (see https://github.com/vercel/next.js/issues/53392) )

Actual result:

The server action sends back a redirect instruction among the response headers:

X-Action-Redirect: /en/search/hello

but the page does not go to that path

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct  9 15:34:04 UTC 2
Binaries:
  Node: 21.1.0
  npm: 10.2.0
  Yarn: 1.22.15
  pnpm: 6.11.0
Relevant Packages:
  next: 14.0.2
  eslint-config-next: 14.0.2
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A

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

App Router

Additional context

When I try the same but first opening http://localhost:3000/en/search/world and then try to redirect via the server action then it works without any issues.

I’ve also wrote down all of the infos above to the readme in my repo.

About this issue

  • Original URL
  • State: open
  • Created 8 months ago
  • Reactions: 9
  • Comments: 43 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Off topic, but I have to admit that I regret choosing Next 13 for a new project. This whole next generation of next.js kinda feels like a work-in-progress/proof-of-concept project rather than something that is production ready.

I’ve spent way too many frustrating hours on this. For me, the work around is to just have a common root layout with minimum markup.

Project structure: image

Example of /app/layout.tsx

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
        <body>{children}</body>
    </html>
  )
}

Still not working in 14.0.3

This actually does work for me for all redirects apart from redirect('/') or redirect('').

I have two root layouts in seperate groups. Where a server action from the login page does a redirect().

image

Having a root layout fixed my problem. My folder looks like this: app -> layout.tsx (I needed to add this root layout) app -> (auth) -> layout.tsx app -> (home) -> layout.tsx

@meszaros-lajos-gyorgy I have just amended your code slightly below and the redirect works as expected:

Add layout.tsx in src/app:-

export const metadata = {
  title: "Next.js",
  description: "Generated by Next.js",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Amend src/app/[locale]/layout.tsx to:

import { SiteSearch } from "@/components/SiteSearch";

export default function LocaleLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <SiteSearch />
      {children}
    </div>
  );
}

Amend src/app/search/layout.tsx:

import { SiteSearch } from "@/components/SiteSearch";

export default function SearchLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <SiteSearch />
      {children}
    </div>
  );
}

Amend src/app/search/[search]/page.tsx to get the params:

export default async function SearchPageEN(args: any) {
  return (
    <div>
      <h1>/en/search/{args.params.search}</h1>
    </div>
  );
}

let us know if it all works for you.

Actually, I did get this working as I wanted, bear with me, I am new to NextJs and what I am doing maybe not what is intended but for me, all of my errors have gone and the redirect works as expected along with the different layouts rendering how I want them.

In my scenario I wanted 2 layouts, the anon layout and the landing layout. The only difference between those two is that I also have a Navbar in the landing layout.

@guscsales is correct, you need the root layout and the other layouts become nested layouts (I think).

So what I did is have the layout.tsx in the root of the app, with this being the root layout, it holds all of the top level stuff. (fyi, I have ShadCn also installed) and the other layouts are in route groups (anon) and (landing)

import { Montserrat as FontSans } from "next/font/google";
import "@/styles/globals.css";

const defaultUrl = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : "http://localhost:3000";

export const metadata = {
  metadataBase: new URL(defaultUrl),
  title: "My NextJs and Supabase site",
  description: "The fastest way to build apps with Next.js and Supabase",
};

import { cn } from "@/lib/utils";

export const fontSans = FontSans({
  subsets: ["latin"],
  variable: "--font-sans",
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={cn(
          "min-h-screen bg-background font-sans text-foreground antialiased",
          fontSans.variable,
        )}
      >
        {children}
      </body>
    </html>
  );
}

The (anon)/layout.tsx looks like this:-

export default function AnonLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex min-h-screen flex-col items-center">{children}</div>
  );
}

The (landing)/layout.tsx looks like this:-

import Navbar from "@/components/Navbar";

export default function LandingLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="min-h-screen bg-background font-sans text-foreground antialiased">
      <Navbar />
      {children}
    </div>
  );
}

In my (anon)/login/page.tsx I have my signin method redirect as below.

  const signIn = async (formData: FormData) => {
    "use server";

    const email = formData.get("email") as string;
    const password = formData.get("password") as string;
    const supabase = createClient();

    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      return redirect("/login?message=Could not authenticate user");
    }

    return redirect("/");
  };

Apologies for the messy response, just wanted to let peeps know how I am doing this. Feel free to correct me, as I said, I am new to NextJs.

It maybe that the problem derives from the fact that you can/should only have one <html><body> so don’t put them in anything else except the root layout.tsx.

wow, same issue. cant use with condition

Still broken in v14.0.5-canary.43

Version 14.0.4-canary.39 still broken

The issue is still present in 14.0.4-canary.18