Most web applications use one or more of the layout patterns below. Below are various best practices and templates using Tailwind classes.
Parallel Routes
my-next-app/
├── ...
├── app/
│ ├── @modal
│ │ ├── post
│ │ │ └── [id]
│ │ │ └── page.tsx
│ │ └── default.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── default.tsx
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ui/
│ │ └── Modal.tsx
└── ...
export default function ModalPage() {
const router = useRouter()
const { id } = useParams()
const [selectedData, setSelectedData] = useState<DataType | null>(null)
useEffect(() => {
if (id) {
const data = dummyData.find((item) => item.id === Number(id))
setSelectedData(data || null)
} else {
setSelectedData(null)
}
}, [id])
const handleClose = () => {
router.back()
}
return (
<Modal open={Boolean(id && selectedData)} onClose={handleClose}>
{selectedData && (
<div>
<h2 className='font-bold'>{selectedData.title}</h2>
<p>{selectedData.body}</p>
</div>
)}
</Modal>
)
}
export default function Default() {
return null
}
import Home from './page'
export default function Default() {
return <Home />
}
export default function RootLayout({
children,
modal,
}: Readonly<{
children: React.ReactNode
modal: React.ReactNode
}>) {
return (
<html lang='en'>
<body className={inter.className}>
{modal}
{children}
</body>
</html>
)
}
export default function Home() {
const router = useRouter()
const handleOpen = (data: DataType) => {
if (data && data.id) {
router.push(`/post/${data.id}`)
} else {
console.error('Invalid data:', data)
}
}
console.log('Home render')
return (
<div className='flex flex-col h-full items-center'>
<div data-role='navigation-bar' className='flex border-b w-full px-4 sm:px-6 py-4'>
<span className='text-black font-bold'>Instagram</span>
</div>
<div data-role='main' className='h-full w-full px-4 sm:px-6 py-4 bg-gray-100'>
<div className='grid grid-cols-3 gap-2 max-w-4xl mx-auto'>
{dummyData.map((data) => (
<div key={data.id} onClick={() => handleOpen(data)} className='bg-white p-4 aspect-square'>
<h2 className='font-bold'>{data.title}</h2>
<p>{data.body}</p>
</div>
))}
</div>
</div>
</div>
)
}
import { type PropsWithChildren } from 'react'
import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react'
interface ModalProps {
open: boolean
onClose: () => void
}
export const Modal = ({ open, onClose, children }: PropsWithChildren<ModalProps>) => {
return (
<Dialog open={open} onClose={onClose} className='relative z-50'>
{/* The backdrop, rendered as a fixed sibling to the panel container */}
<DialogBackdrop className='fixed inset-0 bg-black/30' />
{/* Full-screen container to center the panel */}
<div className='fixed inset-0 flex w-screen items-center justify-center p-4'>
{/* The actual dialog panel */}
<DialogPanel className='max-w-xl bg-white p-4 w-full rounded-lg'>{children}</DialogPanel>
</div>
</Dialog>
)
}