The “View” Is Amazing – Creating Views
The Historical Bible App – Part 4
"Taste and see that the LORD is good; blessed is the one who takes refuge in Him." – Psalm 34 : 8
Our database tables are alive and Eloquent models are humming, time to let users see something. This chapter dives into view‑layer choices, folder structure, layouts, and finally renders our first People page.
1. View‑Layer Flavours
Framework What It Is Pros Cons Blade Laravel's native templating (PHP) Simple, zero JS build‑step, great for SSR Limited interactivity; sprinkle‑JS required Livewire Reactive components over Blade via WebSockets/AJAX SPA‑like UX with pure PHP Can feel "magic"; heavy DOM patching on big pages Vue Progressive JS framework (official Laravel Breeze option) Smooth learning curve, great ecosystem Extra bundle; not everyone loves templates Angular Full‑fat framework (RxJS, DI, tooling) Enterprise‑grade structure Steep learning curve; heavyweight for small apps React (+ Inertia) Component‑based UI + Inertia bridge Our chosen stack; no separate API; rich component libraries Needs TypeScript & build tooling
No single winner, pick the tool that best fits your team & project shape. We're sticking with React 19 + Inertia because it dovetails nicely with Laravel routes and lets us share validation & auth logic.
2. Front‑End Project Anatomy
resources/
└─ js/ ├─ Pages/ # Route-level screens (People/Index.tsx, Dashboard.tsx…) ├─ Components/ # Reusable UI bits (Button.tsx, Badge.tsx…) ├─ lib/ # helpers or services (api.ts, date.ts…) ├─ Layouts/ # Shells that wrap pages (AppLayout.tsx…) └─ index.tsx # Inertia bootstrap
You can dump everything in one folder, future‑you will weep. Stick to a convention.
CSS & Tailwind
Tailwind is already wired via Vite (tailwind.config.js). You rarely touch public/css; instead, sprinkle utility classes right in JSX.
UI Components: shadcn/ui
Installed by the Laravel React starter. Run:
npx shadcn-ui@latest add button card dialog
and voilà, accessible, themeable primitives without reinventing wheels.
3. Layouts – One Shell to Rule Them All
Create resources/js/Layouts/AppLayout.tsx
import { ReactNode } from "react"
import { Link, usePage } from "@inertiajs/react"
export default function AppLayout({ children }: { children: ReactNode }) { const { auth } = usePage().props return ( <div className="min-h-screen flex flex-col bg-muted/50"> <header className="bg-white border-b"> <div className="container mx-auto flex items-center justify-between py-4"> <Link href="/" className="text-lg font-bold">Historical Bible</Link> <span className="text-sm">👋 {auth.user?.name ?? "Guest"}</span> </div> </header> <main className="flex-1 container mx-auto py-8">{children}</main> </div> )
}
Pages now wrap with:
export default function Dashboard() { return ( <AppLayout> {/* page content */} </AppLayout> )
}
4. Building a Reusable Component
resources/js/Components/Badge.tsx
import { cva, type VariantProps } from "class-variance-authority"
const badgeVariants = cva( "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold", { variants: { variant: { default: "bg-primary/10 text-primary", success: "bg-green-100 text-green-800", danger: "bg-red-100 text-red-800", }, }, defaultVariants: { variant: "default" }, }
)
type BadgeProps = VariantProps<typeof badgeVariants> & { children: React.ReactNode }
export function Badge({ variant, children }: BadgeProps) { return <span className={badgeVariants({ variant })}>{children}</span>
}
Use anywhere:
<Badge variant="success">Verified</Badge>
5. Creating the People Index Page
Backend Route & Controller
routes/web.php
Route::get('/people', [PeopleController::class,'index'])->name('people.index');
app/Http/Controllers/PeopleController.php
public function index()
{ $people = People::with('era')->paginate(25); return Inertia::render('People/Index', [ 'people' => $people ]);
}
Front‑End Page
resources/js/Pages/People/Index.tsx
import { Head, Link, usePage } from "@inertiajs/react"
import AppLayout from "@/Layouts/AppLayout"
import { Badge } from "@/Components/Badge"
import { People } from "@/types"
export default function PeopleIndex() { const { people } = usePage().props as { people: { data: People[] } } return ( <AppLayout> <Head title="People" /> <h1 className="text-2xl font-bold mb-6">People</h1> <div className="overflow-x-auto rounded-lg border"> <table className="min-w-full bg-white text-sm"> <thead> <tr className="bg-muted/20 text-left"> <th className="px-4 py-2">Name</th> <th className="px-4 py-2">Title</th> <th className="px-4 py-2">Era</th> <th className="px-4 py-2">Certainty</th> </tr> </thead> <tbody> {people.data.map(p => ( <tr key={p.id} className="border-t"> <td className="px-4 py-2"> <Link href={route('people.show', p.id)} className="hover:underline"> {p.name} </Link> </td> <td className="px-4 py-2">{p.title ?? ', '}</td> <td className="px-4 py-2">{p.era?.name ?? ', '}</td> <td className="px-4 py-2"> <Badge variant={p.certainty > 80 ? 'success' : p.certainty > 50 ? 'default' : 'danger'}> {p.certainty}% </Badge> </td> </tr> ))} </tbody> </table> </div> </AppLayout> )
}
Fire up npm run dev, hit /people, and bask in freshly rendered history.
6. What's Next?
Show view pages (
People/Show.tsx) * Add create or edit forms with Zod + Inertia Form * Sprinkle Livewire‑like real‑time features via Echo & Pusher (optional)
Until then, keep your components dry, your layouts flex, and your views amazing.
Tom signing off – see you in part 5!


