NextJS 13 如何实现页面完全加载后再显示
问题描述
我是NextJS前端新手,正在进行功能练习。希望实现页面在所有资源(包括所有HTTP请求获取响应后)加载完成后再显示。具体场景:使用Magic Link进行身份验证,希望导航栏直接显示已登录用户的邮箱/用户名,避免先显示硬编码的'anonymous'再延迟更新为实际邮箱的情况。
现有代码如下:
首页 page.js
import Banner from "@/components/banner/banner"; import NavBar from "@/components/navbar/navbar"; import CardSections from "@/components/card-sections/card-sections"; import {getVideos} from "@/libs/videos"; async function getVideosApi() { return getVideos() } export default async function Home() { const disneyVideos = await getVideosApi() return ( <main className={'main'}> <NavBar/> <Banner title={'Titanic - the movie'} subTitle={'Sad story about Titanic'} imgUrl={'/static/titanic.webp'}/> <CardSections title={'Disney'} videos={disneyVideos}></CardSections> </main> ) }
NavBar组件
"use client" import styles from "./navbar.module.css"; import {useRouter} from "next/navigation"; import Link from "next/link"; import {useEffect, useState} from "react"; import {magic} from "@/app/login/magic-link"; export default function NavBar() { const [showDropdown, setShowDropdown] = useState(false) const [username, setUsername] = useState("anonymous") const router = useRouter(); useEffect(() => { async function getUsername() { try { const { email } = await magic.user.getMetadata(); if (email) { console.log(email); setUsername(email); } } catch (error) { console.log("Error retrieving email:", error); } } getUsername(); }, []); const handleOnClickHome = (e) => { e.preventDefault(); router.push('/') } const handleOnClickMyList = (e) => { e.preventDefault(); router.push('/browse/my-list') } const handleDropdown = (e) => { e.preventDefault(); setShowDropdown(!showDropdown) } return ( <div className={styles.container}> <div className={styles.wrapper}> <a className={styles.logoLink}> <div className={styles.logoWrapper}>Netflix</div> </a> <ul className={styles.navItems}> <li className={styles.navItem} onClick={handleOnClickHome}>Home</li> <li className={styles.navItem} onClick={handleOnClickMyList}>My List</li> </ul> <nav className={styles.navContainer}> <div> <button onClick={handleDropdown} className={styles.usernameBtn}> <p className={styles.username}>{username}</p> </button> {showDropdown && <div> <Link href={'/login'}>Sign In </Link> </div> } </div> </nav> </div> </div> ); };
Magic Link 初始化代码
import {Magic} from 'magic-sdk'; const createMagic = (key) => { return typeof window !== 'undefined' && new Magic(key); }; export const magic = createMagic("pk_live_*****");
解决方案
由于Magic SDK依赖浏览器环境(需要window对象),无法在Next.js服务器组件中直接获取用户信息,所以需要在客户端组件中处理加载状态,避免显示临时的'anonymous'文本。以下提供两种可行方案:
方案一:手动控制加载状态
修改NavBar组件,将初始用户名设为null,并添加加载状态标记,直到用户信息获取完成后再显示实际内容:
"use client" import styles from "./navbar.module.css"; import {useRouter} from "next/navigation"; import Link from "next/link"; import {useEffect, useState} from "react"; import {magic} from "@/app/login/magic-link"; export default function NavBar() { const [showDropdown, setShowDropdown] = useState(false) const [username, setUsername] = useState(null) const [isLoading, setIsLoading] = useState(true) const router = useRouter(); useEffect(() => { async function getUsername() { try { const { email } = await magic.user.getMetadata(); // 未登录时显示"Sign In"而非"anonymous" setUsername(email || "Sign In"); } catch (error) { console.log("Error retrieving email:", error); setUsername("Sign In"); } finally { // 无论请求成功或失败,结束加载状态 setIsLoading(false); } } getUsername(); }, []); // 加载中时显示加载状态的导航栏 if (isLoading) { return ( <div className={styles.container}> <div className={styles.wrapper}> <a className={styles.logoLink}> <div className={styles.logoWrapper}>Netflix</div> </a> <ul className={styles.navItems}> <li className={styles.navItem} onClick={(e) => {e.preventDefault(); router.push('/')}}>Home</li> <li className={styles.navItem} onClick={(e) => {e.preventDefault(); router.push('/browse/my-list')}}>My List</li> </ul> <nav className={styles.navContainer}> <div> <button className={styles.usernameBtn} disabled> <p className={styles.username}>Loading...</p> </button> </div> </nav> </div> </div> ); } const handleOnClickHome = (e) => { e.preventDefault(); router.push('/') } const handleOnClickMyList = (e) => { e.preventDefault(); router.push('/browse/my-list') } const handleDropdown = (e) => { e.preventDefault(); setShowDropdown(!showDropdown) } return ( <div className={styles.container}> <div className={styles.wrapper}> <a className={styles.logoLink}> <div className={styles.logoWrapper}>Netflix</div> </a> <ul className={styles.navItems}> <li className={styles.navItem} onClick={handleOnClickHome}>Home</li> <li className={styles.navItem} onClick={handleOnClickMyList}>My List</li> </ul> <nav className={styles.navContainer}> <div> <button onClick={handleDropdown} className={styles.usernameBtn}> <p className={styles.username}>{username}</p> </button> {showDropdown && ( <div> <Link href={'/login'}>Sign In</Link> </div> )} </div> </nav> </div> </div> ); };
方案二:使用React Suspense异步加载用户信息
将用户信息的获取逻辑封装成独立的异步客户端组件,通过Suspense组件实现自动加载状态管理:
1. 创建异步用户信息组件
// components/UserInfo.js "use client" import {magic} from "@/app/login/magic-link"; export default async function UserInfo() { try { const { email } = await magic.user.getMetadata(); return email || "Sign In"; } catch (error) { console.log("Error retrieving email:", error); return "Sign In"; } }
2. 修改NavBar组件,引入Suspense
"use client" import styles from "./navbar.module.css"; import {useRouter} from "next/navigation"; import Link from "next/link"; import {useState, Suspense} from "react"; import UserInfo from "./UserInfo"; export default function NavBar() { const [showDropdown, setShowDropdown] = useState(false) const router = useRouter(); const handleOnClickHome = (e) => { e.preventDefault(); router.push('/') } const handleOnClickMyList = (e) => { e.preventDefault(); router.push('/browse/my-list') } const handleDropdown = (e) => { e.preventDefault(); setShowDropdown(!showDropdown) } return ( <div className={styles.container}> <div className={styles.wrapper}> <a className={styles.logoLink}> <div className={styles.logoWrapper}>Netflix</div> </a> <ul className={styles.navItems}> <li className={styles.navItem} onClick={handleOnClickHome}>Home</li> <li className={styles.navItem} onClick={handleOnClickMyList}>My List</li> </ul> <nav className={styles.navContainer}> <div> <button onClick={handleDropdown} className={styles.usernameBtn}> <Suspense fallback={<p className={styles.username}>Loading...</p>}> <p className={styles.username}><UserInfo /></p> </Suspense> </button> {showDropdown && ( <div> <Link href={'/login'}>Sign In</Link> </div> )} </div> </nav> </div> </div> ); };
内容的提问来源于stack exchange,提问作者ROZZ
相关产品推荐
相关产品推荐

