vercel / next.js

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

Intercepting routes do not work with `redirect` from `next/navigation` #54676

Closed acifani closed 11 months ago

acifani commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:05 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.12.0
      npm: 9.6.0
      Yarn: 1.22.19
      pnpm: N/A
    Relevant Packages:
      next: 13.4.20-canary.10
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.2.2
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App Router, Routing (next/router, next/navigation, next/link)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/acifani/intercepting-route-server-action/

To Reproduce

To reproduce the bug from my repo

  1. Go to https://intercepting-route-server-action.vercel.app/
  2. Insert a random string or number in With server/client redirect
  3. Click on Go

Generally

Have an intercepted page (see example structure from repro repo) and navigate to the route of that page using redirect from next/navigation.

The bug appears both on Vercel and locally.

Describe the Bug

This is the file structure in the repro project

app
├── @intercept
│   ├── (.)foo
│   │   └── [id]
│   │       └── page.tsx
│   └── default.tsx
├── foo
│   └── [id]
│       └── page.tsx
├── layout.tsx
└── page.tsx

Using router.push from useRouter works just as expected:

// working router.push
'use client'
import { useRouter } from 'next/navigation';

export function ClientUseRouter() {
  const router = useRouter();

  function onSubmit(e: FormEvent) {
    e.preventDefault();
    router.push(`/foo/${e.target[0].value}`);
  }

  return (
    <form action="" onSubmit={onSubmit}>
      <input type="text" name="id" placeholder="id" />
      <button>Go</button>
    </form>
  );
}

But using redirect from next/navigation does not work. A network call is made, but the page is not shown. The bug appears both in client components or server components using Server Actions

// broken client redirect
'use client'
import { redirect } from 'next/navigation';

export function ClientRedirect() {
  function onSubmit(e: FormEvent) {
    e.preventDefault();
    redirect(`/foo/${e.target[0].value}`, 'push');
  }

  return (
    <form action="" onSubmit={onSubmit}>
      <input type="text" name="id" placeholder="id" />
      <button>Go</button>
    </form>
  );
}
// broken server redirect
import { redirect } from 'next/navigation';

async function navigate(data: FormData) {
  'use server';
  redirect(`/foo/${data.get('id')}`);
}

export function ServerRedirect() {
  return (
    <form action={navigate}>
      <input type="text" name="id" placeholder="id" />
      <button>Go</button>
    </form>
  );
}

Expected Behavior

The intercepted page should work the same way for router.push and redirect

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

akomm commented 1 year ago

Think there is a bug with redirect not working if you use absolute url like /foobar and you have to use foobar. Can't find the issue again, but there is one.

noccer commented 1 year ago

I'm having the same issue. It's my first time using redirect and it plain doesn't work for a simple thing like redirecting to /

akomm commented 1 year ago

A bit upsetting: In the last cannary versions I don't see (at least in change list) any fixes. There is so much blocking breakage in routing and parallel routes, its scary. About to jump of on new projects from using next, were we already initiated and are in early phase of development. Lack of proper marking of experimental / alpha state code is something every dev should pay strong attention to. Going back to the stack used in previous projects that work well. I just hoped some layout and routing improvements that I've seen in nextjs13, but exactly that is very broken out of experimental phase.

Diego-0110 commented 1 year ago

The documentation says (https://nextjs.org/docs/app/api-reference/functions/redirect):

Invoking the redirect() function throws a NEXT_REDIRECT error and terminates rendering of the route segment in which it was thrown.

In Server Components and Client Components, you should use redirect() only during its rendering (just like the example showed in the documentation). To do redirection in other situations you can use useRouter() (router.push()).

I would suggest to clarify this in the documentation, because the first time I read it I thought I could use redirect() just like router.push().

yuxiaoy1 commented 1 year ago

Having the same issue, I'm using it in a server action function which handles a search input server component, It's annoying that it only succueed serveral times then the whole page goes to no responding. Please fix the behavior.

Diego-0110 commented 1 year ago

Having the same issue, I'm using it in a server action function which handles a search input server component, It's annoying that it only succueed serveral times then the whole page goes to no responding. Please fix the behavior.

In the situation you are using redirect it should work, since the documentation shows a similar example. But, note that Server Actions are experimental, so I wouldn't expect it to work as they should be.

Test

I've tried to replicate the issue with a basic test in Next 13.5.4 and redirect works fine.

File structure:

app
├── blog
│   └── page.js
├── actions.js
├── ClientComponent.jsx
├── layout.js
└── page.js

Code:

// app/page.js
import ClientComponent from "./ClientComponent";

export default function Home() {
  return (
    <main>
      <ClientComponent />
    </main>
  )
}
// app/ClientComponent.jsx
'use client'

import { myAction } from './actions'

export default function ClientComponent() {
  return (
    <form action={myAction}>
      <button type="submit">Add to Cart</button>
    </form>
  )
}
// app/actions.js
'use server'

import { redirect } from "next/navigation"

export async function myAction() {
  console.log('Server Action')
  redirect('/blog')
}
// app/blog/page.js
"use client"
import { useState } from "react"

export default function Home() {
  const [counter, setCounter] = useState(0)
  return (
    <main>
      <h1>Blog</h1>
      <h3>{counter}</h3>
      <button type="button" onClick={() => setCounter(counter + 1)}>+</button>
    </main>
  )
}
yuxiaoy1 commented 1 year ago

@Diego-0110 It only suscceed serveral times, when I make some other operations and then trigger it,it would fali and the whole page goes to no responding.

sinafath commented 11 months ago

same issue

Edit by maintainers: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

leerob commented 11 months ago

Thank you for creating a reproduction. I just took a look here and have confirmed this is working as expected on canary based on my understanding. There's an opportunity to make this more clear in our docs, which I will do as well.

My understanding is that server-side redirects with redirect would not intercept the route, but instead, navigate directly to the "regular" page as you've deceived. To intercept, you would use next/link or router.push.

Secondly, redirect is supported for use in Client Components, but not in event handlers. It can be called from a Server Actions, which is imported (or passed as a prop) for usage in a form.

"use client";

import { navigate } from './actions';

export function ClientRedirect() {
  return (
    <form action={navigate}>
      <input type="text" name="id" placeholder="id" />
      <button>Go</button>
    </form>
  );
}
'use server';

import { redirect } from 'next/navigation';

export async function navigate(data: FormData) {
  redirect(`/foo/${data.get('id')}`);
}

https://github.com/vercel/next.js/assets/9113740/083ed400-58cc-4c4d-8e22-5b1e44d788f3

github-actions[bot] commented 10 months ago

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.