【问题标题】:Next.js Redirect from / to another pageNext.js 从/到另一个页面重定向
【发布时间】:2020-01-30 02:20:34
【问题描述】:

我是 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>

【问题讨论】:

  • 您希望重定向何时发生?

标签: html reactjs routing react-router next.js


【解决方案1】:

警告

首先,您应该评估是否需要客户端重定向(在 React 内)、服务器端重定向(301 HTTP 响应)或服务器端重定向 + 身份验证(301 HTTP 响应,但还要检查一些逻辑身份验证)

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

服务器端重定向很诱人,尤其是当您想要“保护”私人页面时,但您应该评估您是否真的需要它们。通常,你不会。它们会引发意想不到的复杂性,例如管理身份验证令牌和刷新令牌。相反,您可能希望在架构中添加网关服务器、反向代理或任何前端服务器,以处理此类检查。

请记住,Next.js 只是 React 应用程序,使用 Next.js 高级功能(如 SSR)需要付出一定的代价,在您的上下文中应该是合理的。

下一个 9.4 答案

您好,这是一个适用于所有场景的示例组件:

Vulcan next starter withPrivate access

Example usage here

答案很庞大,如果我以某种方式违反了 SO 规则,很抱歉,但我不想粘贴 180 行代码。如果您想同时支持 SSR 和静态导出,则 Next 中没有简单的模式来处理重定向。

以下场景都需要特定的模式:

  • 服务器端渲染:如果允许,我们会渲染页面,如果不允许,我们会进行 HTTP 重定向
  • 静态渲染(服务器端):我们什么都不渲染,但我们仍然将页面包含在构建中
  • 客户端渲染,静态导出后:我们检查客户端是否用户是auth,是否重定向。在此检查期间或如果我们正在重定向,我们不会显示任何内容(或加载程序)。
  • 客户端使用 next/router 重定向后的客户端渲染:相同的行为。
  • SSR 后的客户端渲染:我们使用 getInitialProps 传递的道具来判断用户是否被允许,直接在第一次渲染时。只是快一点,避免出现空白闪光。

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

下一个 9.5 更新

正如@Arthur 在 cmets 中所述,9.5 还包括设置redirects in next.config.js 的可能性。 我还不清楚这个功能的局限性,但它们似乎是全局重定向,例如当您需要移动页面或仅在有限的时间内允许访问时。 因此,它们并不意味着例如处理身份验证,因为它们似乎无权访问请求上下文。再次,有待确认。

下 10 个新文档更新

此解决方案特定于根据身份验证的重定向。

Authentication patterns are now documented

我不喜欢从getServerSideProps 进行身份验证,因为在我看来太晚了,并且很难使用高级模式(例如处理刷新令牌)进行设置。但这是官方的解决方案。

您可能还想根据 Vercel 仪表板的工作方式(在撰写本文时)检查记录的 in this ticket 方法,以防止未经身份验证的内容闪现

下一个 10.2 标头和基于 cookie 的重写更新

Next 10.2 引入了基于标头和 cookie 的 Rewrites。 这是重定向服务器端的好方法,基于身份验证 cookie 或标头的存在。

但是,请记住,这不是安全重定向。用户可以使用虚假令牌更改其请求标头。您仍然需要网关、反向代理或前端服务器来实际检查令牌有效性并正确设置标头。

编辑:注意网址不会改变。重写将 URL 指向应用程序的现有页面,而不更改 URL => 它允许您拥有“虚拟”URL。

示例用例:假设您有一个已翻译的页面src/contact.tsx,并设置了 i18n 重定向。您可以通过将/de/kontact 重写为/de/contact 来翻译页面名称本身(“联系人”)。

下一个 12 更新

现在middlewares 让您可以完全控制服务器端重定向。

但是,请再次记住,大多数情况下,客户端重定向和检查就足够了。

旧答案(有效,但静态渲染会混乱)

半官方示例

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 };
  };

 2. 在 componentDidMount 中重定向(在禁用 SSR 时有用,例如在静态模式下)

这是客户端渲染的后备。

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

我无法避免在静态模式下闪烁初始页面添加这一点,因为您无法在静态构建期间重定向,但它似乎比通常的方法更好。随着我的进步,我会尝试编辑。

Full example is here

Relevant issue, which sadly ends up with a client only answer

New issue I've opened regarding redirecton

【讨论】:

  • 在 v9.5.0 中可以添加重定向到 next.config.js - link。这样你就可以更新你的答案,如果它是相关的信息
  • 谢谢!文档对可以使用重定向的上下文并不完全清楚:它是否在“导出”模式下工作,您是否可以在 SSR 期间访问request 对象?据我了解,这些是全局重定向,例如您移动了一条路线,或者您有一个可用于有限类型的页面,适用于每个用户。需要更多测试/更多反馈来更新我的答案。
  • 嗨@EricBurel。你能帮我解决这个问题吗? stackoverflow.com/questions/69403655/…。谢谢
  • 闻起来像你的也从 /login 页面重定向,所以你得到一个无限循环。
【解决方案2】:

Next.js 10+ 为我们提供了一些额外而优雅的解决方案来进行重定向。

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

    下面的示例假设我们有一些额外的会话要检查(但可以 你想要的任何东西)。如果会话为空并且我们在服务器端 (context.res),这意味着用户没有登录,我们应该 重定向到登录页面 (/login).. 以另一种方式我们可以通过 sessionprops 并重定向到/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'
        }
      }
    }
    
    
  2. 客户端 - 你可以使用例如useRouter hook:

    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 来自哪里?
【解决方案3】:

更新:Next.js >= 12
现在您可以使用middleware 进行重定向,在页面文件夹(或页面内的任何子文件夹)中创建一个_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 将强制应用程序进入SSR,也不支持在构建时重定向,如果重定向在构建时已知,您可以在next.config.js 中添加这些重定向

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

import Router from 'next/router'

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

或者用钩子:

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 重定向甚至更好地使用组件的条件渲染时,通常不是一个好的/优雅的客户端重定向方法。

我已经创建了一个简单的 repo,上面的所有示例 here

【讨论】:

  • 如何使用 React 钩子??
  • 不使用类
  • SSR 呢?使用这种方法,初始页面闪烁
  • @EricBurel OP 明确询问“一旦用户加载页面”顺便检查一下github.com/zeit/next.js/issues/649
  • 同意这个问题并不完全清楚“一旦页面加载意味着”。使用 React-Router 时,SSR 是开箱即用的,因此客户端和服务器之间没有区别。
【解决方案4】:

在 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

【讨论】:

  • 非常感谢您提供斜杠的提示!
【解决方案5】:

适用于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

【讨论】:

  • 我可以知道永久重定向和非永久重定向有什么区别吗?我无法理解非永久重定向。
【解决方案6】:

如果您的目的是确保您的应用像 SPA 一样运行,并希望拦截用户粘贴到地址栏中的传入的无效(或有效)路径名,那么这是一种快速/笨拙的方法。

假设你的路径是,

enum ERoutes {
  HOME = '/',
  ABOUT = '/about',
  CONTACT = '/contact'
}

如果您还没有自定义_error 页面,请添加,并将其添加到其中:

import React from 'react';
import { NextPage } from 'next';
import { useDispatch } from 'react-redux';
import { useRouter } from 'next/router';

const Error: NextPage = () => {
    const { asPath, push } = useRouter();
    const dispatch = useDispatch();

    React.useEffect(() => {
        const routeValid = Object.values(ERoutes).includes(asPath);

        if (routeValid) {
          // do some stuff, such as assigning redux state to then render SPA content in your index page
        } else {
          // you can either continue to render this _error component, or redirect to your index page,
          // where you may have your own error component that is displayed based on your app state.
          // In my case, I always redirect to '/' (as you can see below, where I push('/'), but before doing so,
          // I dispatch relevant redux actions based on the situation
        }

        // I redirect to root always, but you can redirect only if routeValid === true
        push('/');
    }, []);

    return (
        <div>Error because '{asPath}' does not exist</div>
    );
};

export default Error;

【讨论】:

    【解决方案7】:

    这里有 2 个复制粘贴级示例:一个用于浏览器,一个用于服务器。

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

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

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

    客户端

    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
    

    【讨论】:

    【解决方案8】:

    我已经在我的Next.JS 应用程序中通过定义一个根页面来实现这个功能,这会重定向服务器端和客户端。这是根页面的代码:

    import { useEffect } from "react";
    import Router from "next/router";
    
    const redirectTo = "/hello-nextjs";
    
    const RootPage = () => {
      useEffect(() => Router.push(redirectTo));
      return null;
    };
    RootPage.getInitialProps = (ctx) => {
      if (ctx.req) {
        ctx.res.writeHead(302, { Location: redirectTo });
        ctx.res.end();
      }
    };
    
    export default RootPage;
    

    【讨论】:

    • 它很可能会导致静态导出失败,因为 ctx.res.writeHead 未在此上下文中定义。
    【解决方案9】:

    共有三种方法。

    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 文档,&lt;a&gt; 标记是链接中所必需的,用于在新选项卡中打开等内容!

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

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

    【讨论】:

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

    @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 introduced 钩子。这比类更快、更高效。

    【讨论】:

    • This issue 描述了我想要的结果
    • @Arthur 。哦,但是你的问题不是这样说的。 @Nico 和我的答案完全相同,可以替代您在React-router 中使用的&lt;Switch&gt;。即使&lt;Switch&gt; 也不提供任何 303、302 状态码。只是重定向
    • 好吧,我想这里也讨论过。刚刚意识到 NextJS 没有设置任何状态码。 github.com/zeit/next.js/issues/9443
    • 请删除课程。在这里没用。
    【解决方案11】:

    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}/>
      }
    }
    

    【讨论】:

    • 这不应该是公认的答案。根据这个github.com/zeit/next.js/issues/4931#issuecomment-512787861,你不应该重定向到getInitialProps。 @Afsanefda 应该是公认的答案。而且你正在使用 next.js 你不需要反应路由器来组织路由。 Next 默认已经处理了。
    • @rotimi-best,据我所知,我从 next.js 示例中获取了这段代码。另外,我没有使用 react-router,它是作为我想要获得的示例提出的
    • 这是一个有效的答案,但仅适用于 SSR。它不会在静态应用程序中重定向。编辑:实际上它会添加Router.push,但是客户端Router.push 应该进入组件生命周期方法
    猜你喜欢
    • 2016-12-14
    • 1970-01-01
    • 2015-08-14
    • 1970-01-01
    • 2018-12-26
    • 1970-01-01
    • 2014-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多