从/重定向到另一个页面的Next.js重定向

187

我是Next.js的新手,想知道如何将起始页面(/)重定向到例如/hello-nextjs。一旦用户加载页面,确定路径是否为/,就将其重定向到/hello-nextjs

react-router中,我们会做类似以下的操作:

<Switch>
  <Route path="/hello-nextjs" exact component={HelloNextjs} />
  <Redirect to="/hello-nextjs" /> // or <Route path="/" exact render={() => <Redirect to="/hello-nextjs" />} />
</Switch>

1
你想要重定向发生的时候? - Nico
19个回答

310
更新:Next.js >= 13启用AppDir 您可以在客户端组件和服务器组件中使用`next/navigation`进行重定向。
例如,在页面中:
import { redirect } from 'next/navigation';
export default async function Home({ params }) {
    redirect('/hello-nextjs');
  // ...
}

例如,在客户端组件中:

'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export const Home= () => {
  const { push } = useRouter();

  useEffect(() => {
     push('/hello-nextjs');
  }, []);
  return <p></p>;
};

更新:Next.js >= 12.1
正如@warfield在他的答案中指出的那样,从next.js >= 12.1开始,在重定向中不再允许相对URL,并且使用它们将会抛出一个错误。为了更好地展示,我在这里重新发布他的答案:

使用 Next.js >= 12.1 和 middleware 进行重定向:
  1. 在与您的 pages 文件夹相同的级别上创建一个 middleware.ts(或 .js) 文件
  2. 导出一个 middleware 函数
  3. 创建一个 absolute URL 并将其传递给redirect
TypeScript 示例 middleware.ts:


import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {   
  const url = request.nextUrl.clone()   
  if (url.pathname === '/') {
    url.pathname = '/hello-nextjs'
    return NextResponse.redirect(url)   
  } 
}

更新:Next.js >= 12
现在您可以使用中间件来重定向,只需在页面文件夹(或页面内的任何子文件夹)中创建_middleware.js文件即可。

import { NextResponse, NextRequest } from 'next/server'
export async function middleware(req, ev) {
    const { pathname } = req.nextUrl
    if (pathname == '/') {
        return NextResponse.redirect('/hello-nextjs')
    }
    return NextResponse.next()
}

更新:Next.js >= 10

从 Next.js 10 开始,您可以使用 getServerSidePropsgetStaticProps 内部的 redirect 关键字进行服务器端重定向(有关客户端重定向,请参见下文):

export async function getServerSideProps(context) {
  const res = await fetch(`https://.../data`)
  const data = await res.json()
  // or use context.resolvedUrl for conditional redirect
  // if(context.resolvedUrl == "/")
  if (!data) {
    return {
      redirect: {
        destination: '/hello-nextjs',
        permanent: false,
      },
    }
  }

  return {
    props: {}, // will be passed to the page component as props
  }
}

注意:使用getServerSideProps将强制应用程序进行服务器端渲染,同时不支持在构建时进行重定向。如果在构建时已知重定向,则可以将其添加到next.config.js中。

next.js中,您可以在页面加载后使用Router进行重定向,例如:

import Router from 'next/router'

componentDidMount(){
    const {pathname} = Router
    if(pathname == '/' ){
       Router.push('/hello-nextjs')
    }
}

或者使用 Hooks:

import React, { useEffect } from "react";
import Router from 'next/router'

...
useEffect(() => {
   const {pathname} = Router
   if(pathname == '/' ){
       Router.push('/hello-nextjs')
   }
 });

如果你想在重定向之前防止页面闪烁,你可以使用一个简单的技巧:
import React, { useEffect,useState } from "react";
import Router from 'next/router'
const myPage = ()=>{
    const [loaded,setLoaded] = useState(false)
    useEffect(() => {
        const {pathname} = Router
        // conditional redirect
        if(pathname == '/' ){
            // with router.push the page may be added to history
            // the browser on history back will  go back to this page and then forward again to the redirected page
            // you can prevent this behaviour using location.replace
            Router.push('/hello-nextjs')
           //location.replace("/hello-nextjs")
        }else{
            setLoaded(true)
        }
      },[]);

    if(!loaded){
        return <div></div> //show nothing or a loader
    }
    return ( 
        <p>
            You will see this page only if pathname !== "/" , <br/>
        </p> 
    )
}
export default myPage

我会说,通常情况下,在可以使用next.config.js重定向或者更好的条件渲染组件时,进行客户端重定向并不是一个好的/优雅的方法。

我创建了一个简单的代码库,其中包含以上所有示例,在这里查看。


10
SSR怎么样?使用这种方法,初始页面会闪烁。 - Eric Burel
“_middleware” 在自托管时是否可用?因为中间件函数通常作为边缘函数部署。 - JonasLevin
@JonasLevin 是的,它就是。边缘函数只是中间件,但在边缘处。 - Nico
例如,当我将Web应用程序托管在VPS中时,_middleware中的这些函数是否只会在本地运行? - JonasLevin
注意,_middleware 仍处于 Beta 版本(是的,已部署为 Edge Functions),在部署到 Vercel 时可能会抛出错误。 - corysimmons
显示剩余10条评论

78

注意事项

首先,你需要评估是否需要客户端重定向(在React中),服务器端重定向(301 HTTP响应)或者服务器端重定向+身份验证(301 HTTP响应,但也要有一些逻辑来检查身份验证)。

这是我能写的最完整的答案。但是,在大多数情况下,你不需要任何东西。只需像在任何React应用程序中一样进行重定向。首选客户端重定向。只需使用useEffect+router.push即可。

服务器端重定向很诱人,特别是当你想要“保护”私有页面时,但你应该评估是否真的需要它们。通常情况下,你不需要它们。它们引起了意外的复杂性,比如管理授权令牌和刷新令牌。相反,你可能希望添加一个网关服务器、反向代理或其他预处理服务器到你的架构中,以处理这些类型的检查。

请记住,Next.js只是React应用程序,使用Next.js高级功能,如SSR,会带来一些成本,这些成本应该在你的上下文中得到证明。

Next 9.5更新

正如@Arthur在评论中所述,9.5还包括在next.config.js中设置重定向的可能性。 这个功能的限制对我来说还不清楚,但它们似乎是全局重定向,例如当你需要移动一个页面或只允许在有限期间内访问时。 因此,它们并不适用于处理身份验证,因为它们似乎无法访问请求上下文。再次确认。

Next 10新文档更新

这个解决方案特定于根据身份验证进行重定向。

身份验证模式现在已经记录在文档中

我不喜欢从getServerSideProps进行身份验证,因为在我看来,它太晚了,而且在处理刷新令牌等高级模式时可能会很难设置。但这是官方解决方案。

你还可以检查基于Vercel仪表板的此票证所记录的方法,该方法防止未经身份验证的内容闪烁。

Next 10.2基于头和cookie的重写更新

Next 10.2引入了基于头和cookie的重写。 这是一种基于身份验证cookie或标题的服务器端重定向的好方法。

然而,请记住这不是一个安全的重定向。用户可以使用虚假令牌更改其请求标头。您仍需要一个网关、反向代理或前置服务器来实际检查令牌有效性并正确设置标头。
编辑:请注意,URL 不会更改。重写将 URL 指向应用程序中现有页面,而不更改 URL => 它允许您拥有“虚拟” URL。
示例用例:想象一下,您有一个页面 src/contact.tsx,并进行了翻译和 i18n 重定向设置。您可以通过将 /de/kontact 重写为 /de/contact 来翻译页面名称本身(“contact”)。
下一个 12 更新
现在 middlewares 让您完全控制服务器端重定向。
然而,请再次记住,大多数时候客户端重定向和检查就足够了。

过时的 Next 9.4 答案(链接已失效,抱歉)

嗨,这里有一个在所有场景下都有效的示例组件:

Vulcan next starter withPrivate access

示例用法在此处

答案很长,如果我某些方面违反了 SO 规则,请见谅,但我不想粘贴一段 180 行的代码。在 Next 中,没有简单的模式来处理重定向,如果您想同时支持 SSR 和静态导出。

以下各种情况需要特定的模式:

  • 服务器端渲染:如果允许,则渲染页面;否则进行 HTTP 重定向。
  • 静态渲染(服务器端):我们不渲染任何内容,但仍将页面包含在构建中。
  • 客户端渲染,在静态导出后:我们检查客户端是否经过身份验证,并根据需要进行重定向。在此检查期间或者如果我们正在重定向时,我们不显示任何内容(或加载器)。
  • 使用 next/router 进行客户端重定向后的客户端渲染:同样的行为。
  • SSR 后的客户端渲染:我们使用通过 getInitialProps 传递的 props 来告诉是否允许用户在第一次渲染时直接使用。这只是稍微快一点,您可以避免空白闪烁。

在撰写本文时(Next 9.4),您必须使用 getInitialProps,而不是 getServerSideProps,否则将失去使用 next export 的能力。

更过时的旧答案(有效,但静态渲染会很混乱)

半官方示例

with-cookie-auth 示例在 getInitialProps 中重定向。我还不确定它是否是有效的模式,但以下是代码:

Profile.getInitialProps = async ctx => {
  const { token } = nextCookie(ctx)
  const apiUrl = getHost(ctx.req) + '/api/profile'

  const redirectOnError = () =>
    typeof window !== 'undefined'
      ? Router.push('/login')
      : ctx.res.writeHead(302, { Location: '/login' }).end()

  try {
    const response = await fetch(apiUrl, {
      credentials: 'include',
      headers: {
        Authorization: JSON.stringify({ token }),
      },
    })

    if (response.ok) {
      const js = await response.json()
      console.log('js', js)
      return js
    } else {
      // https://github.com/developit/unfetch#caveats
      return await redirectOnError()
    }
  } catch (error) {
    // Implementation or Network error
    return redirectOnError()
  }
}

它处理服务器端和客户端。 fetch 调用实际上是获取身份验证令牌的,您可能希望将其封装到单独的函数中。

我建议的替代方案

 1. 服务器端重定向(避免 SSR 期间的闪烁)

这是最常见的情况。您希望在此时进行重定向,以避免初始页面在第一次加载时闪烁。

MyApp.getInitialProps = async appContext => {
    const currentUser = await getCurrentUser(); // define this beforehand
    const appProps = await App.getInitialProps(appContext);
    // check that we are in SSR mode (NOT static and NOT client-side)
    if (typeof window === "undefined" && appContext.ctx.res.writeHead) {
      if (!currentUser && !isPublicRoute(appContext.router.pathname)) {
          appContext.ctx.res.writeHead(302, { Location: "/account/login" });
          appContext.ctx.res.end();
      }
    }
    return { ...appProps, currentUser };
  };

在componentDidMount中进行重定向 (在静态模式下禁用SSR时非常有用)

这是客户端渲染的后备方案。

  componentDidMount() {
    const { currentUser, router } = this.props;
    if (!currentUser && !isPublicRoute(router.pathname)) {
      Router.push("/account/login");
    }
  }

由于在静态构建期间无法重定向,因此我无法避免在静态模式下闪现初始页面,但这似乎比通常的方法更好。我会在进展中尝试进行编辑。

完整示例在此处

相关问题,可悲的是只有客户端回答

我开了一个新问题,涉及重定向


1
在v9.5.0中,可以通过next.config.js来添加重定向 - 链接。这样,如果有相关信息需要更新,您就可以更新您的答案。 - Arthur
1
谢谢!文档在重定向的使用场景方面并不完全清晰:它是否可以在“导出”模式下工作,您在SSR期间是否可以访问“请求”对象?据我了解,这些是全局重定向,例如您移动了路由或者有一个页面仅对有限类型的用户可用。需要进行更多测试/反馈以更新我的答案。 - Eric Burel
看起来你从/login页面进行了重定向,导致出现了无限循环。 - Eric Burel
1
这些链接已失效,Vulcan下一个starter带有私有访问。示例用法在此处。 - Caleb Taylor
它们无论如何都已经过时了。我编辑了帖子以反映这一点。 - Eric Burel

66

有三种方法。

1.根据事件或函数重定向:

import Router from 'next/router';

<button type="button" onClick={() => Router.push('/myroute')} />

2.使用钩子函数进行重定向:

import Router , {useRouter}  from 'next/router';
    
const router = useRouter()

<button type="button" onClick={() => router.push('/myroute')} />

3.使用链接进行重定向:

根据Nextjs文档,<a>标签在链接中是必需的,例如用于在新标签页中打开链接!

import Link from 'next/link';
     
<Link href="/myroute">
   <a>myroute</a>
</Link>

除了常规的服务器端路由,还有一些其他的选项,其中之一是asPath。在所有描述的方法中,您都可以添加asPath以重定向客户端和服务器端。

编辑于2022年12月13日

1. 使用Link进行重定向不再需要锚标签!

import Link from 'next/link';

<Link href="/myroute">
  my route
</Link>

2. 使用 Next.js 重定向

next.config.js 中。

module.exports = {
  async redirects() {
    return [
      {
        source: '/someroute',
        destination: '/myroute',
        permanent: true,
      },
    ]
  },
}

嗨!你可以看看我的解决方案。 - Arthur
这是一种命令式的方法。根据用户操作进行重定向是可以的,但不能基于页面加载时的条件,就像问题中所述的那样。 - Eric Burel
我不明白你的意思! - Afsanefda
2
问题是关于根据当前路由路径自动重定向。您的答案是有效的,但在这种情况下不适用:它们都需要用户点击。 - Eric Burel
@EricBurel,是的,这不是我想要的,这个答案没有解决我的问题。 - Arthur

19

Next.js 10+提供了一种更加简便优雅的解决方案来进行重定向。

  1. 服务器端 - 应该使用 getServerSideProps

    下面的示例假设我们有一些额外的会话需要检查(但可以是任何你想要的内容)。如果会话为空且我们在服务器端 (context.res),这意味着用户未登录,我们应该重定向到登录页面(/login)。另一种方式是将session传递给props并重定向到/dashboard

import { getSession } from 'next-auth/client';

export const getServerSideProps = async (context) => {
  const session = await getSession(context);
  if(context.res && !session) {
    return {
      redirect: {
        permanent: false,
        destination: '/login'
      }
    }
  }

  return {
    props: { session },
    redirect: {
      permanent: false,
      destination: '/dashboard'
    }
  }
}

  • 客户端 - 您可以使用例如useRouter钩子:

  • import { useRouter } from 'next/router';
    import { useSession } from 'next-auth/client';   
    
    const router = useRouter();
    const [ session, loading ] = useSession();
    
    if (typeof window !== 'undefined' && loading) return null;
    
    if (typeof window !== 'undefined' && !session) {
      router.push('/login');
    }
    
    router.push('/dashboard');
    

    更多信息请查看:https://github.com/vercel/next.js/discussions/14890


    useRouteruseSession 是从哪里来的? - Newbyte
    Next.js的实现 - mercury

    11

    适用于 NextJS 9.5.0+

    1. 创建 next.config.js 文件
    2. 添加源网址和目标网址(如果是外部域名,您可以设置为永久重定向)
    module.exports = {
      async redirects() {
        return [
          {
            source: '/team',
            destination: '/about',
            permanent: false,
          },
          {
            source: "/blog",
            destination:
              "https://blog.dundermifflin.com",
            permanent: true,
          },
        ];
      },
    };
    
    

    https://github.com/vercel/next.js/tree/canary/examples/redirects


    2
    请问永久重定向和非永久重定向有什么区别?我无法理解非永久重定向的概念。 - Sam Kah Chiin

    11

    以下是两个复制粘贴级别的示例:一个用于浏览器,一个用于服务器。

    https://dev.to/justincy/client-side-and-server-side-redirection-in-next-js-3ile

    假设您想将根路径 (/) 重定向到名为 home 的页面:(/home)

    在您的主 index 文件中,粘贴以下内容:

    客户端

    import { useRouter } from 'next/router'
    
    function RedirectPage() {
      const router = useRouter()
      // Make sure we're in the browser
      if (typeof window !== 'undefined') {
        router.push('/home')
      }
    }
    
    export default RedirectPage
    

    服务器端

    import { useRouter } from 'next/router'
    
    function RedirectPage({ ctx }) {
      const router = useRouter()
      // Make sure we're in the browser
      if (typeof window !== 'undefined') {
        router.push('/home');
        return; 
      }
    }
    
    RedirectPage.getInitialProps = ctx => {
      // We check for ctx.res to make sure we're on the server.
      if (ctx.res) {
        ctx.res.writeHead(302, { Location: '/home' });
        ctx.res.end();
      }
      return { };
    }
    
    export default RedirectPage
    

    1
    谢谢。这篇文章中关于Next.js中客户端和服务器端重定向以及HOC抽象的解释非常有价值! - Kojo
    1
    到目前为止,这是最好的答案,具有最正确的客户端路由器实现。所有其他答案都使用了错误的钩子,或根本没有使用 useRouter() - brandonscript

    7

    @Nico的答案解决了在使用类时的问题。

    如果你正在使用函数,你不能使用componentDidMount。相反,你可以使用React Hooks useEffect

    
    import React, {useEffect} from 'react';
    
    export default function App() {
      const classes = useStyles();
    
      useEffect(() => { 
        const {pathname} = Router
        if(pathname == '/' ){
          Router.push('/templates/mainpage1')
        }  
      }
      , []);
      return (
        null
      )
    }
    

    2019年,React 引入了hooks。与类相比,hooks更快、更高效。


    这个问题描述了我想要的结果。链接 - Arthur
    @Arthur。哦,但是你的问题没有说明。@Nico 和我的答案完全相同,是 React-Router<Switch> 的替代品。即使 <Switch> 也不提供任何 303、302 状态码。只会重定向。 - Trect
    好的,我认为这里讨论的也是这个问题。只是意识到NextJS没有设置任何状态码。https://github.com/zeit/next.js/issues/9443 - Trect
    请移除类。它在这里没有用处。 - Pushp Singh

    6
    在NextJs v9.5及以上版本中,您可以在next.config.js文件中配置重定向和重写。但是,如果您使用了 trailingSlash: true,请确保源路径以斜杠结尾以进行正确匹配。
    module.exports = {
      trailingSlash: true,
      async redirects() {
        return [
          {
            source: '/old/:slug/', // Notice the slash at the end
            destination: '/new/:slug',
            permanent: false,
          },
        ]
      },
    }
    

    您还需要考虑其他可能影响路由的插件和配置,比如next-images

    相关文档:https://nextjs.org/docs/api-reference/next.config.js/redirects


    非常感谢您提供的有关尾随斜杠的提示! - Sascha Klatt

    5

    redirect-to.ts

    import Router from "next/router";
    
    export default function redirectTo(
      destination: any,
      { res, status }: any = {}
    ): void {
      if (res) {
        res.writeHead(status || 302, { Location: destination });
        res.end();
      } else if (destination[0] === "/" && destination[1] !== "/") {
        Router.push(destination);
      } else {
        window.location = destination;
      }
    }
    

    _app.tsx

    import App, {AppContext} from 'next/app'
    import Router from "next/router"
    import React from 'react'
    import redirectTo from "../utils/redirect-to"
    
    
    export default class MyApp extends App {
      public static async getInitialProps({Component, ctx}: AppContext): Promise<{pageProps: {}}> {
        let pageProps = {};
    
        if (Component.getInitialProps) {
          pageProps = await Component.getInitialProps(ctx);
        }
    
        if (ctx.pathname === "" || ctx.pathname === "/_error") {
          redirectTo("/hello-next-js", { res: ctx.res, status: 301 }); <== Redirect-To
          return {pageProps};
        }
    
        return {pageProps};
      }
    
      render() {
        const {Component, pageProps} = this.props;
        return <Component {...pageProps}/>
      }
    }
    

    3
    这不应该成为被接受的答案。根据这篇文章 https://github.com/zeit/next.js/issues/4931#issuecomment-512787861,在`getInitialProps`中你不应该进行重定向。@Afsanefda 的回答应该被接受。而且,如果你使用的是 Next.js,你就不需要使用 React Router 来组织路由了。Next.js 默认就已经处理好了。 - rotimi-best
    3
    据我记得,我从Next.js的示例中获取了这段代码。此外,我没有使用react-router,它只是一个我想要实现的示例。 - Arthur
    2
    这是一个有效的答案,但仅适用于SSR。它在静态应用程序中不会重定向。编辑:实际上,如果您添加了Router.push,它将会重定向,但是客户端的Router.push应该放在组件生命周期方法中。 - Eric Burel

    4

    Next.js >= 12.1

    在重定向中不再允许使用相对 URL,否则会抛出以下错误:
    Error: URLs is malformed. Please use only absolute URLs

    使用Next.js >= 12.1的middleware进行重定向:

    1. 在与pages文件夹同级别创建一个名为middleware.ts(或 .js)的文件。
    2. 导出一个middleware函数。
    3. 创建一个绝对 URL并将其传递给redirect函数。

    middleware.ts的TypeScript示例:

    import { NextResponse } from 'next/server'
    import type { NextRequest } from 'next/server'
    
    export function middleware(request: NextRequest) {
      const url = request.nextUrl.clone()
      if (url.pathname === '/') {
        url.pathname = '/hello-nextjs'
        return NextResponse.redirect(url)
      }
    }
    
    

    1
    我正在尝试在注册后实现一个成功页面重定向,这对我的情况效果最佳。 - Victor Eke

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接