Next.js App Router实现刷新仍保留背景的模态框
实现Next.js路由模态框并保留背景页面的规范方案
核心思路
利用Next.js App Router的并行路由特性,让主页面内容与模态框路由段同时渲染,既保持URL状态同步,又避免将layout.tsx当作页面内容容器的不规范写法。
文件夹结构(完全符合App Router规范)
app/ └── lorem/ ├── @modal/ │ └── new/ │ └── page.tsx # 模态框页面组件 ├── layout.tsx # 布局容器,负责同时渲染主内容与模态框 └── page.tsx # 主页面:列表+打开模态框按钮
代码实现
1. 布局文件 lorem/layout.tsx
import { usePathname } from 'next/navigation'; import { Suspense } from 'react'; export default function LoremLayout({ children, modal, }: { children: React.ReactNode; modal: React.ReactNode; }) { const pathname = usePathname(); const isModalActive = pathname === '/lorem/new'; return ( <div className="min-h-screen"> {/* 主页面内容始终渲染 */} {children} {/* 仅当路由匹配时渲染模态框,用Suspense处理加载状态 */} {isModalActive && ( <Suspense fallback={<div className="fixed inset-0 bg-black/20 flex items-center justify-center">加载中...</div>}> {modal} </Suspense> )} </div> ); }
2. 主页面 lorem/page.tsx
'use client'; import { useRouter } from 'next/navigation'; export default function LoremPage() { const router = useRouter(); return ( <div className="p-6 max-w-4xl mx-auto"> <h1 className="text-2xl font-bold mb-4">Lorem 列表页面</h1> <ul className="space-y-2 mb-6"> <li className="p-3 border rounded">列表项 1</li> <li className="p-3 border rounded">列表项 2</li> <li className="p-3 border rounded">列表项 3</li> </ul> <button onClick={() => router.push('/lorem/new')} className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors" > 打开新建模态框 </button> </div> ); }
3. 模态框页面 lorem/@modal/new/page.tsx
'use client'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; export default function NewModal() { const router = useRouter(); // 点击模态框外部关闭 useEffect(() => { const handleOutsideClick = (e: MouseEvent) => { const modalEl = document.getElementById('new-modal-container'); if (modalEl && !modalEl.contains(e.target as Node)) { router.back(); } }; document.addEventListener('mousedown', handleOutsideClick); return () => document.removeEventListener('mousedown', handleOutsideClick); }, [router]); return ( <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div id="new-modal-container" className="bg-white rounded-lg p-6 w-full max-w-md shadow-xl"> <h2 className="text-xl font-semibold mb-4">新建条目</h2> <div className="mb-4"> <input type="text" placeholder="输入内容" className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div className="flex justify-end gap-2"> <button onClick={() => router.back()} className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300 transition-colors" > 取消 </button> <button onClick={() => { // 这里添加表单提交逻辑 router.back(); }} className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors" > 保存 </button> </div> </div> </div> ); }
方案优势
- 完全遵循App Router的设计规范:
page.tsx负责页面内容,layout.tsx仅做布局容器,并行路由@modal独立管理模态框状态 - URL与视图完全同步:刷新
/lorem/new时,主页面列表与模态框会同时渲染 - 无不规范写法:避免了将
layout当作页面内容载体、page返回null的问题 - 可扩展性强:后续可通过修改并行路由规则,轻松添加更多模态框路由
内容的提问来源于stack exchange,提问作者rwacarter
相关产品推荐
相关产品推荐

