In the previous phases, we built the backend API and added real-time updates with WebSockets. Now it's time to bring the frontend to life β building a React + TypeScript + Tailwind Single Page Application (SPA) that talks to our backend.
This post covers Phase 1 of the frontend: setting up the project, defining its folder structure, and configuring routing for multiple pages.
π― Goals of Phase 1
- Set up React project with Vite + TypeScript + Tailwind CSS
- Create a scalable folder structure used in professional projects
- Implement basic routing (Home + Shipments pages)
- Understand SPA architecture and why React Router is needed
π Tech Stack
- React (UI library)
- TypeScript (type safety)
- Vite (fast dev bundler)
- Tailwind CSS (utility-first styling)
- React Router DOM (SPA routing)
1. Project Setup
Create Vite project
npm create vite@latest client-freight
# Choose React + TypeScript
cd client-freight
Install Tailwind (latest)
npm install -D tailwindcss @tailwindcss/vite
Edit vite.config.ts
:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
Add Tailwind directives in index.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
Run:
npm run dev
You should see the default Vite React page at http://localhost:5173
.
2. Professional Folder Structure
Here's the structure we created under src/
:
src/
β
βββ components/ # Reusable UI components (buttons, cards, etc.)
βββ pages/ # Full-page components (HomePage, ShipmentsPage)
βββ hooks/ # Custom React hooks (e.g., useShipments)
βββ services/ # API calls / WebSocket helpers
βββ types/ # TypeScript types/interfaces
βββ context/ # React context (e.g., AuthContext, ThemeContext)
βββ App.tsx # Main app component (routing setup)
βββ main.tsx # Vite entry point
βββ index.css # Tailwind global styles
Why this structure?
-
components/: Small reusable building blocks (e.g.,
<Button />
,<Card />
) -
pages/: Represents real screens/routes (
/
for home,/shipments
for shipments list) - hooks/: Encapsulate logic like data fetching or WebSocket subscriptions
-
services/: All HTTP/WebSocket calls centralized here (e.g.,
getShipments()
) - types/: Centralized TypeScript interfaces to keep backend and frontend consistent
- context/: Global state management (e.g., authentication, theme)
This separation scales well as the project grows and keeps responsibilities clear.
3. What is an SPA and Why React Router?
How SPAs Work
A Single Page Application (SPA) loads one HTML page and dynamically updates the content with JavaScript (React) instead of requesting new HTML pages from the server.
- Faster navigation after first load
- No full-page refresh β feels more like a native app
- Better user experience for dashboards, real-time apps, etc.
Why React Router DOM?
You could theoretically build a single-view SPA with just React and no router at all. React Router DOM becomes necessary when you want that SPA to feel like it has multiple "pages" that users can navigate between.
Without React Router, you'd have to manually manage window.location
and conditional rendering. React Router DOM handles:
- URL-based navigation (
/
,/shipments
) - Back/forward browser buttons
- Page transitions without reloads
- Preserving state between page views
But React itself is what makes SPAs possible by:
- Managing the virtual DOM and efficiently updating only changed parts
- Creating reusable components that can be dynamically rendered
- Handling state changes without page reloads
- Providing the foundation for building interactive UIs
4. Implementing Routing
Install React Router DOM:
npm install react-router-dom
App.tsx
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import HomePage from "./pages/HomePage";
import ShipmentsPage from "./pages/ShipmentsPage";
export default function App() {
return (
<Router>
<nav className="p-4 bg-gray-800 text-white flex gap-4">
<Link to="/">Home</Link>
<Link to="/shipments">Shipments</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/shipments" element={<ShipmentsPage />} />
</Routes>
</Router>
);
}
pages/HomePage.tsx
export default function HomePage() {
return <h1 className="text-2xl font-bold p-4">Welcome to Freight Tracker</h1>;
}
pages/ShipmentsPage.tsx
export default function ShipmentsPage() {
return <h1 className="text-2xl font-bold p-4">Shipments</h1>;
}
How Navigation Works
-
User visits
/
β Sees nav bar +HomePage
-
Clicks "Shipments" link β URL changes to
/shipments
, React swapsHomePage
withShipmentsPage
(no reload) - Nav bar stays persistent across all routes
This is what makes SPAs fast and seamless.
5. Key Learnings from Phase 1
- Tailwind + Vite gives rapid dev speed + modern CSS utilities
- SPA model: one-page load, client-side routing, smooth navigation
- React Router DOM: essential for multiple "pages" in SPAs
- Folder structure: separation of pages, components, services, types keeps things clean
6. What's Next?
- Phase 2 Frontend: Fetch shipments from backend API
- Add real-time WebSocket updates
- Create responsive UI components (cards, tables)
- Later: auth, filtering, deployment
GitHub Repo
Frontend code is available here: GitHub - freight-tracker-frontend
Top comments (0)