You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React/Next中Cookie转LocalStorage及落地页自动登录问题解决

问题描述

我的项目包含4个源,已在用户源实现Cookie转LocalStorage且存储正常,但落地页源无法获取LocalStorage中的token,期望用户登录后落地页自动登录。请问如何在落地页渲染时完成Cookie转LocalStorage并实现自动登录?

落地页相关文件

main.js

import { useContext, createContext, useState, useEffect } from 'react'
// import { setCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { cookies } from 'next/headers'
import { useLogout } from '@/core/services/react-query/auth'

const context = createContext()

export const useMainContext = () => {
    return useContext(context)
}

const MainContextProvider = ({
    children,
    theme: initialTheme,
    token: _token,
  
}) => {
    const [theme, _setTheme] = useState(initialTheme)
    const [token, _setToken] = useState(_token)
    
    const [isOpen, _setIsOpen] = useState(false)

    const router = useRouter()
    const { pathname, asPath, query, locale } = router

    useEffect(() => {  
        const storedTokenFromCookies = document.cookie  
            .split('; ')  
            .find(row => row.startsWith('token='));  
        
        if (storedTokenFromCookies) {  
            const tokenValue = storedTokenFromCookies.split('=')[1];  
            setToken(tokenValue);  // Set token in the state  
            localStorage.setItem('token', tokenValue);  // Also set it in localStorage  
            console.log('User found in cookies:', tokenValue);  

            // Redirect user to profile page  
            router.push('/profile');  
        } else {  
            const storedToken = localStorage.getItem('token');  
            if (storedToken) {  
                setToken(storedToken);  // Update state if the token exists in localStorage  
            }  
        }  
    }, [router]);  

    const toggleTheme = () => {
        const value = theme === 'dark' ? 'light' : 'dark'
        // setCookie('theme', value)
        localStorage.setItem('theme', value)
        _setTheme(value)

        if (value === 'dark') 
        {document.documentElement.classList.add('dark')
        }else {document.documentElement.classList.remove('dark')}
    }

    const toggleLang = () => {
        const lang = locale === 'en' ? 'fa' : 'en'
        router.push({ pathname, query }, asPath, { locale: lang })
        document.documentElement.setAttribute(
            'dir',
            lang === 'en' ? 'ltr' : 'rtl'
        )
        // setCookie('lang', lang, { path: '/' })
        localStorage.setItem('lang', lang)
    }

    const setToken = (value) => {
        // setCookie('token', value)
        localStorage.setItem('token', value)
        _setToken(value)
    }

    const setIsOpen = () => {
        _setIsOpen((prevState) => !prevState)
    }
   
    return (
        <context.Provider
            value={{
                theme,
                toggleTheme,
                toggleLang,
                token,
                setToken,
                setIsOpen,
                isOpen,
            }}>
            {children}
        </context.Provider>
    )
}

export default MainContextProvider

_document.js

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
    static async getInitialProps(ctx) {
        const sheet = new ServerStyleSheet()
        const originalRenderPage = ctx.renderPage
    
        try {
            // Run the React rendering logic synchronously
            ctx.renderPage = () =>
                originalRenderPage({
                    // Useful for wrapping the whole react tree
                    enhanceApp: (App) => (props) =>
                        sheet.collectStyles(<App {...props} />),
                    // Useful for wrapping in a per-page basis
                    enhanceComponent: (Component) => Component,
                })

            // Run the parent `getInitialProps`, it now includes the custom `renderPage`
            const initialProps = await Document.getInitialProps(ctx)
            const theme = ctx.req?.cookies?.theme || 'dark'
            const lang = ctx.req?.cookies?.lang
            const dir = lang === 'en' ? 'ltr' : 'rtl'
            
            return {
                ...initialProps,
                theme,
                dir,
                styles: [initialProps.styles, sheet.getStyleElement()]
            }
        }
        finally {
            sheet.seal()
        } }
    render() {
        return (
            <Html className={this.props.theme} dir={this.props.dir}>
                <Head />
                <body>
                <Main />
                <NextScript />
                </body>
            </Html>
        )}}

export default MyDocument

_app.js

import '@/styles/globals.css'
import Providers from '@/components/layout/Providers'
import { getCookie, setCookie } from 'cookies-next'
import MainLayout from '@/components/layout/main/MainLayout'
import { GlobalStyles } from '@/styles/styled/GlobalStyle.styled'

export default function App({ Component, pageProps, theme, ...rest }) {
    // TODO: if cookie is empty
    let _theme = theme
    if (!_theme) {
        _theme = 'dark'
        setCookie('theme', _theme)
    }

    return (
        <Providers theme={_theme} {...rest}>
            <MainLayout pageProps={pageProps}>
                <GlobalStyles />
                <Component {...pageProps} />
            </MainLayout>
        </Providers>
    )
}

App.getInitialProps = ({ ctx }) => {
    const theme = getCookie('theme', ctx)
    const token = getCookie('token', ctx)

    return {
        theme,
        token,
    }
}

修复方案

1. 优化服务端到客户端的token同步逻辑

现有代码在main.js的useEffect中直接读取document.cookie,存在SSR时document未定义的风险,且没有利用_app.js中已经从服务端获取到的token。修改main.js的MainContextProvider:

const MainContextProvider = ({
    children,
    theme: initialTheme,
    token: _token,
  
}) => {
    const [theme, _setTheme] = useState(initialTheme)
    // 直接用服务端传来的token初始化状态
    const [token, _setToken] = useState(_token)
    
    const router = useRouter()

    useEffect(() => {  
        // 优先同步服务端的token到LocalStorage
        if (_token) {
            localStorage.setItem('token', _token);
            // 仅在当前不在profile页时跳转,避免循环
            if (router.pathname !== '/profile') {
                router.push('/profile');
            }
        } else {
            // 服务端未拿到token时,再读取客户端Cookie
            const storedTokenFromCookies = document.cookie  
                .split('; ')  
                .find(row => row.startsWith('token='));  
            
            if (storedTokenFromCookies) {  
                const tokenValue = storedTokenFromCookies.split('=')[1];  
                setToken(tokenValue);  
                localStorage.setItem('token', tokenValue);  
                if (router.pathname !== '/profile') {
                    router.push('/profile');  
                }
            } else {  
                // 读取LocalStorage中已有的token
                const storedToken = localStorage.getItem('token');  
                if (storedToken) {  
                    setToken(storedToken);  
                }  
            }  
        }
    }, [router, _token]); // 添加_token为依赖,确保服务端传值时触发同步

    // 其余代码保持不变
}

2. 确保token正确传递到Context Provider

修改_app.js,将getInitialProps中获取的token明确传递给Providers(假设Providers内部包裹了MainContextProvider,需要透传token属性):

export default function App({ Component, pageProps, theme, token }) {
    let _theme = theme
    if (!_theme) {
        _theme = 'dark'
        setCookie('theme', _theme)
    }

    return (
        // 明确传入token到Providers
        <Providers theme={_theme} token={token}>
            <MainLayout pageProps={pageProps}>
                <GlobalStyles />
                <Component {...pageProps} />
            </MainLayout>
        </Providers>
    )
}

// getInitialProps代码保持不变

3. 跨源场景的额外处理

如果落地页和用户源属于不同域名,Cookie默认无法跨域共享,需要:

  • 在用户源设置Cookie时,指定domain为公共父域名(如两个域名都是.yourdomain.com),同时设置sameSite: 'Lax'(或None,需配合secure: true)
  • 若为完全独立的域名,可通过加密的URL参数传递token,或采用OAuth2授权码模式实现跨域登录

4. 验证逻辑有效性

  • 落地页加载时,打开浏览器控制台,执行localStorage.getItem('token')确认值存在
  • 检查MainContext中的token状态是否正确更新
  • 确认自动跳转仅在未登录状态触发,避免无限循环

内容的提问来源于stack exchange,提问作者fgkh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:55:56