-
Notifications
You must be signed in to change notification settings - Fork 0
Session 6 WebApp Frontend
Welcome to Session 6! In this session, we'll focus on building the frontend of our web application using Next.js, a powerful React framework. Next.js provides features like server-side rendering, static site generation, and simplified routing that make it an excellent choice for modern web applications.
- Understand what Next.js is and its advantages
- Set up a Next.js project
- Learn React fundamentals
- Create responsive layouts with Tailwind CSS
- Build reusable components
- Implement client-side routing
- Fetch data from APIs
Next.js is a React framework that provides structure, features, and optimizations for your application.
- Server-Side Rendering (SSR): Renders pages on the server for better SEO and performance
- Static Site Generation (SSG): Pre-renders pages at build time for faster load times
- File-based Routing: Creates routes based on your file structure
- API Routes: Allows you to build API endpoints as part of your Next.js app
- Automatic Code Splitting: Loads only the code needed for each page
- Built-in CSS and Sass Support: Allows importing CSS/Sass files directly
- Fast Refresh: Preserves component state during development
# Create a new Next.js app
npx create-next-app my-todo-app
cd my-todo-app
# Start the development server
npm run devmy-todo-app/
├── node_modules/
├── public/ # Static assets
├── src/
│ ├── app/ # App router pages and layouts
│ │ ├── page.js # Home page (/route)
│ │ └── layout.js # Root layout
│ ├── components/ # Reusable components
│ └── styles/ # CSS files
├── .eslintrc.json
├── next.config.js # Next.js configuration
├── package.json # Project dependencies
└── README.md
Next.js is built on top of React, so understanding React fundamentals is essential.
Components are the building blocks of React applications. They are reusable pieces of code that return HTML elements.
// Simple functional component
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Using the component
<Greeting name="John" />Props (short for properties) are how you pass data to components.
function TodoItem({ task, completed, onToggle }) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={completed}
onChange={onToggle}
/>
<span style={{ textDecoration: completed ? 'line-through' : 'none' }}>
{task}
</span>
</div>
);
}Hooks are functions that let you "hook into" React state and lifecycle features.
import { useState, useEffect } from 'react';
function TodoList() {
// State hook for managing tasks
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
// Effect hook for fetching tasks
useEffect(() => {
// Fetch tasks from API
fetch('/api/tasks')
.then(response => response.json())
.then(data => setTasks(data));
}, []); // Empty dependency array means this runs once when component mounts
// Function to add a new task
const addTask = () => {
if (newTask.trim()) {
setTasks([...tasks, { id: Date.now(), text: newTask, completed: false }]);
setNewTask('');
}
};
return (
<div>
<h1>Todo List</h1>
<div>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Add a new task"
/>
<button onClick={addTask}>Add</button>
</div>
<ul>
{tasks.map(task => (
<li key={task.id}>
<TodoItem
task={task.text}
completed={task.completed}
onToggle={() => {
setTasks(tasks.map(t =>
t.id === task.id ? { ...t, completed: !t.completed } : t
));
}}
/>
</li>
))}
</ul>
</div>
);
}Tailwind CSS is a utility-first CSS framework that allows you to build custom designs without leaving your HTML.
# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pConfigure Tailwind in your tailwind.config.js:
module.exports = {
content: [
'./src/app/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}Add Tailwind directives to your CSS:
/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;Use Tailwind classes in your components:
function Button({ children, onClick }) {
return (
<button
onClick={onClick}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
{children}
</button>
);
}Next.js provides a file-system based routing mechanism where the app directory defines routes:
src/app/
├── page.js # Home route (/)
├── about/
│ └── page.js # About route (/about)
├── tasks/
│ ├── page.js # Tasks list route (/tasks)
│ └── [id]/
│ └── page.js # Dynamic task route (/tasks/1, /tasks/2, etc.)
└── layout.js # Root layout (applies to all routes)
Linking between pages:
import Link from 'next/link';
function Navigation() {
return (
<nav>
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/tasks">Tasks</Link>
</li>
</ul>
</nav>
);
}Next.js provides several methods for fetching data:
// This component fetches data on the server
async function TaskList() {
const res = await fetch('https://api.example.com/tasks', { cache: 'no-store' });
const tasks = await res.json();
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
export default TaskList;'use client';
import { useState, useEffect } from 'react';
function TaskList() {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchTasks() {
try {
const res = await fetch('/api/tasks');
const data = await res.json();
setTasks(data);
} catch (error) {
console.error('Error fetching tasks:', error);
} finally {
setLoading(false);
}
}
fetchTasks();
}, []);
if (loading) return <p>Loading tasks...</p>;
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
export default TaskList;Let's structure our Todo app frontend:
-
Components:
-
TaskList: Displays all tasks -
TaskItem: Individual task with toggle and delete functionality -
TaskForm: Form for adding new tasks -
Layout: Common layout for all pages
-
-
Pages:
- Home: Landing page
- Tasks: List of all tasks
- Task Detail: View/edit a specific task
Here's a sample implementation of the TaskItem component using Tailwind CSS:
'use client';
function TaskItem({ task, onToggleComplete, onDelete }) {
return (
<div className="flex items-center justify-between p-4 mb-2 bg-white rounded shadow">
<div className="flex items-center">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggleComplete(task.id)}
className="w-5 h-5 mr-3 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span className={`text-lg ${task.completed ? 'line-through text-gray-500' : 'text-gray-800'}`}>
{task.title}
</span>
</div>
<div>
<button
onClick={() => onDelete(task.id)}
className="px-3 py-1 text-sm text-white bg-red-500 rounded hover:bg-red-600"
>
Delete
</button>
</div>
</div>
);
}
export default TaskItem;- Set up a new Next.js project with Tailwind CSS
- Create a simple landing page with a navigation menu
- Build a task list page that displays mock tasks
- Create a form to add new tasks to the list
- Implement client-side form validation
- Add the ability to mark tasks as complete and delete tasks
- Next.js Documentation
- React Documentation
- Tailwind CSS Documentation
- React Hooks Reference
- Next.js GitHub Examples
Now that you've learned how to build the frontend of a web application with Next.js, you're ready to move on to Session 7: WebApp - Backend where you'll learn how to create a backend API using Django Ninja.