From “Working Pages” to Reusable UI
Introduction: From “Working Pages” to Reusable UI
Welcome back! 🎉 You already have a real UI foundation in place. Global styling works, the dashboard routes are set up, navigation exists, and both / and /tasks fetch live task data from the backend.
In this lesson, you’ll make the UI feel more like a real product by reducing duplication and introducing reusable building blocks. Instead of repeating the same markup and styles in multiple places, you’ll start extracting shared pieces into components that can be reused across the app.
There are three main improvements in this lesson. First, you’ll extract the repeated “task row” markup into a dedicated TaskRow component so task lists stay consistent. Next, you’ll introduce a reusable Button component so actions share the same look and behavior. Finally, you’ll evolve the layout into a sidebar-based dashboard shell that highlights the active route, which makes the app feel more structured and easier to navigate.
This keeps the codebase cleaner now, but it also matters later. As the app grows, reusable components make new features faster to build because you can extend existing UI patterns instead of rebuilding them from scratch.
Previously…
In the previous lesson, you enabled Tailwind styling globally, built a dashboard route group with a shared layout and navigation, and fetched tasks from GET /api/tasks while rendering loading, error, and empty states.
Now you’ll take that working UI and organize it into reusable pieces. This is an important step in frontend development: a page can be “working,” but still need cleanup before it becomes easy to maintain.
There is still one key rule that matters throughout this lesson. Don’t modify anything under src/app/api/**. The backend is complete, and this lesson is frontend-only, so your job is to build better UI on top of the existing API rather than changing the API to make the UI easier.
The Golden Rule: Backend is Read-Only
Your UI consumes existing endpoints like: GET /api/tasks → { data: Task[], meta: { timestamp } }
That response shape is why you always parse tasks from json.data. The backend has already defined the contract, and the frontend should follow that contract consistently. In real projects, this kind of discipline matters because the frontend and backend are often developed separately, and changing one side casually can break the other.
A strong Codex prompt for this unit should always keep the scope tight and the constraints explicit. It should restrict edits to a small list of frontend files, and it should clearly include the rule “do not touch src/app/api/**.” That way, Codex is guided toward the exact work you want instead of making broad changes that solve the wrong problem.
