如何在重定向前在 Next.js 中设置 flash 变量?
How can I set a flash variable in Next.js before a redirect?
Laravel 在 PHP 中使用 https://laravel.com/docs/9.x/session#flash-data 使这变得容易,所以我认为 Next.js 也会有一个简单的方法。
我以为我可以做类似的事情:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
ctx.res.setHeader("yourFlashVariable", "yourFlashValue");
console.log('headers', ctx.res.getHeaders()); // Why is it not even appearing here?
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const props = ...
return { props };
};
然后在我的另一页:
export const getServerSideProps: GetServerSideProps = async (context) => {
const { headers, rawHeaders } = context.req;
// look inside the headers for the variable
// ...
但是 header 没有出现。
如果您知道如何实现 flash 变量的目标(即使不使用 headers),我对任何方法都感兴趣。
(最初我问 How can I show a toast notification when redirecting due to lack of session using Next-Auth in Next.js? 但现在觉得我应该问这个更笼统的问题。)
更新
感谢 的合理建议,因此尝试了。
不幸的是,index.tsx
仍然没有从 getFlash
得到任何值。
// getFlash.ts
import { Session } from 'next-session/lib/types';
export default function getFlash(session: Session) {
// If there's a flash message, transfer it to a context, then clear it.
const { flash = null } = session;
console.log({ flash });
// eslint-disable-next-line no-param-reassign
delete session.flash;
return flash;
}
// getNextSession.ts
import nextSession from 'next-session';
export default nextSession();
// foo.tsx
import { getSession } from 'next-auth/react';
import { GetServerSideProps, InferGetServerSidePropsType, NextApiRequest, NextApiResponse } from 'next';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
const req = ctx.req as NextApiRequest;
const res = ctx.res as NextApiResponse;
const nSession = await getNextSession(req, res);
nSession.flash = 'You must be logged in to access this page.'; // THIS LINE CAUSES A WARNING
console.log({ nSession });
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// ...
return { props };
};
// index.tsx
import { GetServerSideProps } from 'next';
import getFlash from '../helpers/getFlash';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getNextSession(context.req, context.res);
let toast = getFlash(session);
console.log({ toast });
if (!toast) {
toast = 'no toast';
}
console.log({ toast });
return {
props: { toast }, // will be passed to the page component as props
};
};
此外,nSession.flash =
行会导致此警告:
warn - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
你的第一个代码对我来说工作正常(在终端中打印 headers)。但是,该组合不会按预期工作,因为您在 /foo
中设置的 header(例如)将连同状态代码 307 和位置 header 一起发送到浏览器/
个。现在“浏览器”将重定向到该位置,它不会转发您的 header。相似话题:https://whosebug.com/a/30683594, https://whosebug.com/a/12883411.
要克服这个问题,您可以这样做。这是有效的,因为浏览器确实发送了 cookie(在这种情况下,在您创建 session 时设置)。
// lib/session.ts
import type { IronSessionOptions } from 'iron-session'
import type { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'sid',
cookieOptions: { secure: process.env.NODE_ENV === 'production' },
}
declare module 'iron-session' {
interface IronSessionData {
flash?: string | undefined
}
}
export const withSessionRoute = (handler: NextApiHandler) =>
withIronSessionApiRoute(handler, sessionOptions)
export const withSessionSsr = <P extends Record<string, unknown> = Record<string, unknown>>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) => withIronSessionSsr(handler, sessionOptions)
// pages/protected.tsx
import type { NextPage } from 'next'
import { getSession } from 'next-auth/react'
import { withSessionSsr } from 'lib/session'
const ProtectedPage: NextPage = () => <h1>Protected Page</h1>
const getServerSideProps = withSessionSsr(async ({ req, res }) => {
const session = await getSession({ req })
if (!session) {
req.session.flash = 'You must be logged in to access this page.'
await req.session.save()
return { redirect: { destination: '/', permanent: false } }
}
return { props: {} }
})
export default ProtectedPage
export { getServerSideProps }
// pages/index.tsx
import type { InferGetServerSidePropsType, NextPage } from 'next'
import { withSessionSsr } from 'lib/session'
const IndexPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ flash }) => {
// TODO: use `flash`
}
const getServerSideProps = withSessionSsr(async ({ req }) => {
// if there's a flash message, transfer
// it to a context, then clear it
// (extract this to a separate function for ease)
const { flash = null } = req.session
delete req.session.flash
await req.session.save()
return { props: { flash } }
})
export default IndexPage
export { getServerSideProps }
如果您想在 API 路由而不是页面中设置闪存数据,这也适用:
import { withSessionRoute } from 'lib/session'
const handler = withSessionRoute(async (req, res) => {
req.session.flash = 'Test'
await req.session.save()
res.redirect(307, '/')
})
export default handler
完整示例:https://github.com/brc-dd/next-flash/tree/with-iron-session
Laravel 在 PHP 中使用 https://laravel.com/docs/9.x/session#flash-data 使这变得容易,所以我认为 Next.js 也会有一个简单的方法。
我以为我可以做类似的事情:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
ctx.res.setHeader("yourFlashVariable", "yourFlashValue");
console.log('headers', ctx.res.getHeaders()); // Why is it not even appearing here?
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const props = ...
return { props };
};
然后在我的另一页:
export const getServerSideProps: GetServerSideProps = async (context) => {
const { headers, rawHeaders } = context.req;
// look inside the headers for the variable
// ...
但是 header 没有出现。
如果您知道如何实现 flash 变量的目标(即使不使用 headers),我对任何方法都感兴趣。
(最初我问 How can I show a toast notification when redirecting due to lack of session using Next-Auth in Next.js? 但现在觉得我应该问这个更笼统的问题。)
更新
感谢
不幸的是,index.tsx
仍然没有从 getFlash
得到任何值。
// getFlash.ts
import { Session } from 'next-session/lib/types';
export default function getFlash(session: Session) {
// If there's a flash message, transfer it to a context, then clear it.
const { flash = null } = session;
console.log({ flash });
// eslint-disable-next-line no-param-reassign
delete session.flash;
return flash;
}
// getNextSession.ts
import nextSession from 'next-session';
export default nextSession();
// foo.tsx
import { getSession } from 'next-auth/react';
import { GetServerSideProps, InferGetServerSidePropsType, NextApiRequest, NextApiResponse } from 'next';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
const req = ctx.req as NextApiRequest;
const res = ctx.res as NextApiResponse;
const nSession = await getNextSession(req, res);
nSession.flash = 'You must be logged in to access this page.'; // THIS LINE CAUSES A WARNING
console.log({ nSession });
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// ...
return { props };
};
// index.tsx
import { GetServerSideProps } from 'next';
import getFlash from '../helpers/getFlash';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getNextSession(context.req, context.res);
let toast = getFlash(session);
console.log({ toast });
if (!toast) {
toast = 'no toast';
}
console.log({ toast });
return {
props: { toast }, // will be passed to the page component as props
};
};
此外,nSession.flash =
行会导致此警告:
warn - You should not access 'res' after getServerSideProps resolves. Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
你的第一个代码对我来说工作正常(在终端中打印 headers)。但是,该组合不会按预期工作,因为您在 /foo
中设置的 header(例如)将连同状态代码 307 和位置 header 一起发送到浏览器/
个。现在“浏览器”将重定向到该位置,它不会转发您的 header。相似话题:https://whosebug.com/a/30683594, https://whosebug.com/a/12883411.
要克服这个问题,您可以这样做。这是有效的,因为浏览器确实发送了 cookie(在这种情况下,在您创建 session 时设置)。
// lib/session.ts
import type { IronSessionOptions } from 'iron-session'
import type { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'sid',
cookieOptions: { secure: process.env.NODE_ENV === 'production' },
}
declare module 'iron-session' {
interface IronSessionData {
flash?: string | undefined
}
}
export const withSessionRoute = (handler: NextApiHandler) =>
withIronSessionApiRoute(handler, sessionOptions)
export const withSessionSsr = <P extends Record<string, unknown> = Record<string, unknown>>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) => withIronSessionSsr(handler, sessionOptions)
// pages/protected.tsx
import type { NextPage } from 'next'
import { getSession } from 'next-auth/react'
import { withSessionSsr } from 'lib/session'
const ProtectedPage: NextPage = () => <h1>Protected Page</h1>
const getServerSideProps = withSessionSsr(async ({ req, res }) => {
const session = await getSession({ req })
if (!session) {
req.session.flash = 'You must be logged in to access this page.'
await req.session.save()
return { redirect: { destination: '/', permanent: false } }
}
return { props: {} }
})
export default ProtectedPage
export { getServerSideProps }
// pages/index.tsx
import type { InferGetServerSidePropsType, NextPage } from 'next'
import { withSessionSsr } from 'lib/session'
const IndexPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ flash }) => {
// TODO: use `flash`
}
const getServerSideProps = withSessionSsr(async ({ req }) => {
// if there's a flash message, transfer
// it to a context, then clear it
// (extract this to a separate function for ease)
const { flash = null } = req.session
delete req.session.flash
await req.session.save()
return { props: { flash } }
})
export default IndexPage
export { getServerSideProps }
如果您想在 API 路由而不是页面中设置闪存数据,这也适用:
import { withSessionRoute } from 'lib/session'
const handler = withSessionRoute(async (req, res) => {
req.session.flash = 'Test'
await req.session.save()
res.redirect(307, '/')
})
export default handler
完整示例:https://github.com/brc-dd/next-flash/tree/with-iron-session