Overview
The application uses Next.js App Router’s layout system to create a consistent structure across pages. Layouts are composed of responsive grid systems, scrollable containers, and semantic HTML elements.
Root Layout
The root layout is defined in src/app/layout.tsx and wraps all pages in the application.
import type { Metadata } from "next" ;
import { Archivo , Geist , Geist_Mono } from "next/font/google" ;
import "./globals.css" ;
import { Toaster } from "@/components/ui/sonner"
const geistSans = Geist ({
variable: "--font-geist-sans" ,
subsets: [ "latin" ],
});
const archivo = Archivo ({
variable: "--font-archivo" ,
subsets: [ "latin" ],
});
const geistMono = Geist_Mono ({
variable: "--font-geist-mono" ,
subsets: [ "latin" ],
});
export const metadata : Metadata = {
title: "Last Ping || Sheduling Messages" ,
description: "Still Alive..." ,
};
export default function RootLayout ({
children ,
} : Readonly <{
children : React . ReactNode ;
}>) {
return (
< html lang = "en" >
< body
className = { ` ${ geistSans . variable } ${ geistMono . variable } ${ archivo . variable } antialiased` }
>
< Toaster />
{ children }
</ body >
</ html >
);
}
Root Layout Features
Font Configuration
Loads Google Fonts (Geist Sans, Geist Mono, Archivo) as CSS variables const geistSans = Geist ({
variable: "--font-geist-sans" ,
subsets: [ "latin" ],
});
Metadata
Defines SEO metadata for the application export const metadata : Metadata = {
title: "Last Ping || Sheduling Messages" ,
description: "Still Alive..." ,
};
Global Toaster
Includes the Sonner toast notification system
Font Variables
Applies font CSS variables to the body element className = { ` ${ geistSans . variable } ${ geistMono . variable } ${ archivo . variable } antialiased` }
The antialiased class applies font smoothing for better text rendering across browsers.
Main Page Layout
The main page layout demonstrates responsive design and component composition.
import Messages from "@/components/Messages" ;
import People from "@/components/People" ;
import Rescheduler from "@/components/Rescheduler" ;
import { Badge } from "@/components/ui/badge" ;
import { Clock } from "lucide-react" ;
export const dynamic = 'force-dynamic' ;
export default async function page () {
if ( ! process . env . BACKEND_URL ) {
return < p > Backend URL not found </ p > ;
}
let data ;
try {
const response = await fetch ( ` ${ process . env . BACKEND_URL } /time-left` , {
method: "GET" ,
});
data = await response . json ();
if ( response . status != 200 ) {
< div className = "h-screen grid place-items-center" >
< h1 className = "font-bold text-4xl" > Backend Cannot Be Accessed </ h1 >
</ div > ;
}
} catch ( e ) {
console . error ( e );
return (
< div className = "h-screen grid place-items-center" >
< h1 className = "font-bold text-4xl" > Backend Cannot Be Accessed </ h1 >
</ div >
);
}
return (
< div className = "container p-5 mx-auto max-w-5xl" >
< div className = "flex justify-center items-center pb-3 border-b-2 border-dashed gap-3 text-primary" >
< Badge variant = "default" className = "text-sm font-bold p-2 px-2" >
< Clock className = "size-4" /> { data . timeLeft } h
</ Badge >
< h1 className = "text-lg font-bold" > Message Scheduler </ h1 >
< Rescheduler />
</ div >
< div className = "grid grid-cols-1 md:grid-cols-3 gap-2 pt-2" >
< Messages />
< People />
</ div >
</ div >
);
}
Page Layout Structure
< div className = "container p-5 mx-auto max-w-5xl" >
{ /* Page content */ }
</ div >
Classes explained:
container - Responsive container with padding
p-5 - Padding on all sides
mx-auto - Horizontal centering
max-w-5xl - Maximum width constraint (896px)
< div className = "grid grid-cols-1 md:grid-cols-3 gap-2 pt-2" >
< Messages />
< People />
</ div >
Classes explained:
grid - CSS Grid layout
grid-cols-1 - 1 column on mobile
md:grid-cols-3 - 3 columns on medium screens and up
gap-2 - Space between grid items
pt-2 - Top padding
Responsive Design
The application uses Tailwind’s responsive breakpoints for mobile-first design.
Breakpoint System
Breakpoints
Mobile First Approach
sm: 640px /* @media (min-width: 640px) */
md: 768px /* @media (min-width: 768px) */
lg: 1024px /* @media (min-width: 1024px) */
xl: 1280px /* @media (min-width: 1280px) */
2xl: 1536px /* @media (min-width: 1536px) */
Responsive Patterns
Mobile (< 768px)
Single column layout
Messages above People
Full width components
Desktop (≥ 768px)
3-column grid layout
Messages: 2 columns (md:col-span-2)
People: 1 column
Side-by-side view
Component Layout Patterns
Messages Component Layout
src/components/Messages.tsx
export default async function Messages () {
const data = await fetch ( ` ${ process . env . BACKEND_URL } /messages/all` , {
headers: {
Authorization: `Basic ${ Buffer . from (
` ${ process . env . USERNAME } : ${ process . env . PASSWORD } `
). toString ( "base64" ) } ` ,
},
});
if ( ! data . ok ) {
notFound ();
}
const messages = await data . json ();
return (
< div className = "md:col-span-2" >
< h2 className = "text-xl font-bold mb-2" > Messages : </ h2 >
< ScrollArea className = "max-h-[calc(100vh-180px)] overflow-y-auto pr-4 mb-3" >
{ messages . map (( message : Message ) => (
< Message key = { message . id } message = { message } people = { people } />
)) }
</ ScrollArea >
< Dialog >
< DialogTrigger asChild >
< Button className = "w-full cursor-pointer flex justify-center items-center" >
< MailPlus />
< span > Add Message </ span >
</ Button >
</ DialogTrigger >
< DialogContent >
< DialogTitle className = "text-lg font-semibold text-center" >
Add Message
</ DialogTitle >
< CreateMessageForm people = { people } />
</ DialogContent >
</ Dialog >
</ div >
);
}
Layout Features:
Column Span
md:col-span-2 makes Messages take 2/3 of the grid on desktop
Section Header
text-xl font-bold mb-2 for the “Messages :” title
Scrollable Area
max-h-[calc(100vh-180px)] ensures list fits in viewport
Full Width Button
w-full makes the “Add Message” button span the container
People Component Layout
src/components/People.tsx
export default async function People () {
const data = await fetch ( ` ${ process . env . BACKEND_URL } /people/all` , {
headers: {
Authorization: `Basic ${ Buffer . from (
` ${ process . env . USERNAME } : ${ process . env . PASSWORD } `
). toString ( "base64" ) } ` ,
},
});
if ( ! data . ok ) {
notFound ();
}
const people : People [] = await data . json ();
return (
< div className = "cursor-pointer mt-6 md:mt-0" >
< h2 className = "text-xl font-bold mb-2" > People : </ h2 >
< ScrollArea className = "max-h-[calc(100vh-180px)] overflow-y-auto pr-4 mb-3" >
{ people . map (( person : People ) => (
< Person key = { person . id } person = { person } />
)) }
</ ScrollArea >
< Dialog >
< DialogTrigger asChild >
< Button className = "w-full cursor-pointer flex items-center justify-center" >
< UserPlus />
< span > Add Person </ span >
</ Button >
</ DialogTrigger >
< DialogContent >
< DialogTitle className = "text-lg font-semibold text-center" >
Add Person
</ DialogTitle >
< PersonCreate />
</ DialogContent >
</ Dialog >
</ div >
);
}
Layout Features:
mt-6 md:mt-0 - Top margin on mobile (spacing from Messages), removed on desktop
Takes 1/3 of grid width on desktop (default, no col-span needed)
Same ScrollArea pattern as Messages
Both Messages and People use ScrollArea for long lists:
< ScrollArea className = "max-h-[calc(100vh-180px)] overflow-y-auto pr-4 mb-3" >
{ items . map (( item ) => (
< Item key = { item . id } item = { item } />
)) }
</ ScrollArea >
Classes explained:
max-h-[calc(100vh-180px)] - Maximum height based on viewport
100vh - Full viewport height
-180px - Subtract space for header, footer, padding
overflow-y-auto - Vertical scrolling when content overflows
pr-4 - Right padding to prevent scrollbar overlap
mb-3 - Bottom margin for spacing before button
The calc() function ensures the scrollable area adapts to different screen heights while preventing page-level scrolling.
Card Layout Pattern
Individual items use the Card component with consistent spacing:
< Card className = "border-1 border-dashed drop-shadow-xl gap-0 mb-2 cursor-pointer" >
< CardHeader className = "font-bold" >
{ message . content . slice ( 0 , 100 ) } ...
</ CardHeader >
< CardContent className = "space-y-2" >
< CardDescription >
Send to: { message . sendTo . name } ( { message . sendToPhone } )
</ CardDescription >
< CardDescription >
Send after: { message . sendAfter } Days
</ CardDescription >
</ CardContent >
</ Card >
Styling breakdown:
border-1 border-dashed - Dashed border style
drop-shadow-xl - Large drop shadow for depth
gap-0 - No gap between card sections
mb-2 - Bottom margin between cards
cursor-pointer - Pointer cursor on hover
space-y-2 - Vertical spacing between descriptions
Error State Layout
The application shows error states with centered layouts:
< div className = "h-screen grid place-items-center" >
< h1 className = "font-bold text-4xl" > Backend Cannot Be Accessed </ h1 >
</ div >
Classes explained:
h-screen - Full viewport height
grid place-items-center - Center content both horizontally and vertically
font-bold text-4xl - Large, bold text for visibility
Dialog Layout
Dialogs are centered and responsive:
src/components/ui/dialog.tsx
function DialogContent ({ className , children , ... props }) {
return (
< DialogPortal >
< DialogOverlay />
< DialogPrimitive.Content
className = { cn (
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg" ,
className
) }
{ ... props }
>
{ children }
< DialogPrimitive.Close className = "absolute top-4 right-4" >
< XIcon />
</ DialogPrimitive.Close >
</ DialogPrimitive.Content >
</ DialogPortal >
)
}
Positioning:
fixed top-[50%] left-[50%] - Fixed position at center
translate-x-[-50%] translate-y-[-50%] - Translate back by 50% to truly center
z-50 - High z-index to appear above other content
max-w-lg - Maximum width constraint (512px)
w-full - Full width up to max-w-lg
max-w-[calc(100%-2rem)] - Prevents dialog from touching screen edges on mobile
Spacing System
The application uses Tailwind’s spacing scale consistently:
Spacing Scale
Common Patterns
0: 0px
1: 0 .25rem (4px)
2: 0 .5rem (8px)
3: 0 .75rem (12px)
4: 1rem (16px)
5: 1 .25rem (20px)
6: 1 .5rem (24px)
Layout Best Practices
Mobile First Start with mobile layout, add responsive classes for larger screens
Consistent Spacing Use Tailwind’s spacing scale for predictable layouts
Semantic HTML Use proper HTML elements (header, main, section, etc.)
Accessibility Ensure keyboard navigation and screen reader support
Server Components by Default
All layout components are Server Components unless they need interactivity:
// Server Component (default) - fetches data on server
export default async function Messages () {
const data = await fetch ( ... );
// ...
}
Server Components reduce JavaScript bundle size and improve initial page load performance.
Force Dynamic Rendering
The page uses export const dynamic = 'force-dynamic' to prevent caching:
export const dynamic = 'force-dynamic' ;
This ensures fresh data on every request, which is important for a messaging application.
Next Steps
Components Overview Learn about the component architecture
Responsive Design Deep dive into responsive patterns