Next.js is an open-source web development framework created by the private company Vercel providing React-based web applications with server-side rendering and static rendering.
Key Next.js Concepts
- File-based routing: Every file in
/app
is a page. - SSR vs. SSG: Choose between Server-side Rendering and Static Site Generation.
- API Routes: Define backend API inside
/app/api
. - Server vs. Client Components: Server components run on the backend, client components use
"use client"
. - Middleware & Edge Functions: For request handling and rapid execution.
Creating Next.js app
Installation
From the terminal / command tool window:
On choosing options selection:npx create-next-app@latest <appName>
- Would you like to use TypeScript? choose Yes
- Would you like to use ESLint? choose Yes
- Would you like to ise Tailwind CSS? choose Yes
- Would you like your code inside a `src/` directory? choose No
- Would you like to use App Router? choose Yes
- Would you like to use Turbopack for `next dev`? choose No
- Would you like to customize the import alias (`@/*`)? choose No
Visit: http://localhost:3000 to see the app.
-
File-based Routing
Each file inside
/app
defines a route automatically./app/page.tsx
→/
(Home page)/app/about/page.tsx
→/about
Create a Basic Page
Create
/app/about/page.tsx
:export default function AboutPage() { return <h1>About Me</h1>; }
Accessible at
/about
. -
Dynamic Routing
For dynamic pages, create a folder with
[param]
notation.Create
/app/blog/[id]/page.tsx
:export default function BlogPost({ params }: { params: { id: string } }) { return <h1>Blog Post ID: {params.id}</h1>; }
Accessible at
/blog/1
,/blog/2
, etc. -
API Routes
Use
/app/api
for backend routes.Create
/app/api/hello/route.ts
:export async function GET() { return Response.json({ message: "Hello from Next.js API!" }); }
Accessible at
/api/hello
. -
Fetch Data on the Server (SSR)
Server-side fetching is done inside
page.tsx
.export default async function Page() { const res = await fetch("https://jsonplaceholder.typicode.com/posts/1"); const post = await res.json(); return <h1>{post.title}</h1>; }
-
Client vs. Server Components
- By default, components in
/app
are Server Components. - To use state, effects, or event listeners, add
"use client"
at the top.
Example:
"use client"; import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increase</button> </div> ); }
- By default, components in
-
Middleware
Middleware allows you to handle requests before they reach the page.
Create
/middleware.ts
:import { NextResponse } from 'next/server'; export function middleware(req) { if (!req.cookies.get('auth')) { return NextResponse.redirect(new URL('/login', req.url)); } }
-
Deployment
Deploy on Vercel:
npm run build vercel
Next.js vs. Express.js?
Feature | Express.js | Next.js |
---|---|---|
Routing | Manual route definitions | Automatic based on file structure |
Rendering | Manual template/data handling | Supports SSR, SSG, ISR |
API | Custom REST or GraphQL | Built-in API routes under /app/api |
Optimization | Manual performance tweaks | Automatic optimizations |
When to Use Next.js Over Express?
- ✅ Need backend + frontend in one framework.
- ✅ Better SEO with Server-side rendering.
- ✅ Simple API routing without Express setup.
- ✅ Automatic optimizations and better performance.
❌ If you're building a pure API backend, Express is still a better choice.
Optimized React Workflow for updating Frontend Data
React is a component-based JavaScript library for building interactive UIs. The most optimized workflow follows a structure where:
- ✅ UI is split into reusable components.
- ✅ Hooks are used for state management & side effects.
- ✅ Props are used for passing data between components.
- ✅ Context API or Redux is used for global state.
-
Creating a Functional Component
A React component is just a JavaScript function that returns JSX (HTML-like syntax).
export default function MyComponent() { return <h1>Hello, React!</h1>; }
-
Using Props for Dynamic Content
Props (properties) allow passing data from **parent to child components**.
function Greeting({ name }) { return <h1>Hello, {name}!</h1>; } export default function App() { return <Greeting name="Bob" />; }
➡ Output:
Hello, Bob!
-
Using State with
useState
useState
allows components to manage their own state."use client"; import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increase</button> </div> ); }
-
Fetching Data with
useEffect
useEffect
is used for fetching data and handling side effects."use client"; import { useEffect, useState } from "react"; export default function DataFetcher() { const [data, setData] = useState(null); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/posts/1") .then((res) => res.json()) .then((data) => setData(data.title)); }, []); // Empty array = run once on mount return <h1>{data ? data : "Loading..."}</h1>; }
-
Handling Forms
Forms use state to manage input values.
"use client"; import { useState } from "react"; export default function FormExample() { const [input, setInput] = useState(""); function handleSubmit(e) { e.preventDefault(); alert("Submitted: " + input); } return ( <form onSubmit={handleSubmit}> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button type="submit">Submit</button> </form> ); }
-
Lifting State Up
To share state between components, **lift it up** to the closest common parent.
function Child({ count }) { return <h1>Count: {count}</h1>; } export default function Parent() { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>Increase</button> <Child count={count} /> </div> ); }
-
Global State with Context API
Context API allows sharing global state without prop drilling.
import { createContext, useContext, useState } from "react"; const ThemeContext = createContext(); function ThemeProvider({ children }) { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } function ThemedComponent() { const { theme, setTheme } = useContext(ThemeContext); return ( <div> <p>Current theme: {theme}</p> <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> Toggle Theme </button> </div> ); } export default function App() { return ( <ThemeProvider> <ThemedComponent /> </ThemeProvider> ); }
-
Conditional Rendering
Use conditional logic to render UI dynamically.
function ConditionalRender({ isLoggedIn }) { return isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in.</h1>; }
-
List Rendering with
map()
Rendering lists from an array.
const users = ["Alice", "Bob", "Charlie"]; export default function UserList() { return ( <ul> {users.map((user, index) => ( <li key={index}>{user}</li> ))} </ul> ); }
Performance Optimization
- ✅ Use
useMemo
for expensive calculations. - ✅ Use
useCallback
to prevent unnecessary re-renders. - ✅ Use React Suspense for lazy loading components.
Why Use This Workflow?
- Reusability: Each component is modular and reusable.
- Performance: State updates are efficient, preventing unnecessary renders.
- Scalability: Hooks and Context API make it easy to scale apps.
- Maintainability: Code is clean and structured.
This React workflow follows best practices and works well with Next.js and standalone React apps.