- Vrealmatic
- TypeSript
- Next.js
TypeScript with NextJS introduction
Starter configuration of a basic Next.js application with TypeScript. Updated for Next.js 16 (App Router).

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
/appis 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". - Proxy (formerly Middleware): Request handling before a route is rendered (redirects, rewrites, auth checks).
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 the recommended Next.js defaults? choose No, customize settings
- Would you like to use TypeScript? choose Yes
- Which linter would you like to use?? choose ESLint
- Would you like to use React Compiler? choose No
- Would you like to ise Tailwind CSS? choose Yes
- Would you like your code inside a `src/` directory? choose Yes
- Would you like to use App Router? choose Yes
- Would you like to customize the import alias (`@/*`)? choose No
Launch the app
cd <appName> npm run devVisit: http://localhost:3000 to see the app.
File-based Routing
Each file inside
/appdefines 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 async function BlogPost({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; return <h1>Blog Post ID: {id}</h1>; }Accessible at
/blog/1,/blog/2, etc.Since Next.js 15,
paramsandsearchParamsare Promises and have to be awaited. The synchronous access shown in older tutorials (params.id) no longer works in Next.js 16.API Routes
Use
/app/apifor 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>; }Since Next.js 15,
fetchrequests are not cached by default. To cache the response, use e.g.fetch(url, { next: { revalidate: 3600 } })or the"use cache"directive (Cache Components) in Next.js 16.Client vs. Server Components
- By default, components in
/appare 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
Proxy (formerly Middleware)
Proxy allows you to handle requests before they reach the page. In Next.js 16,
middleware.tswas renamed toproxy.ts(the exported function toproxy) and runs on the Node.js runtime. The oldmiddleware.tsis deprecated.Create
/proxy.ts(or/src/proxy.tswhen using thesrcdirectory):import { NextResponse, type NextRequest } from 'next/server'; export function proxy(req: NextRequest) { if (!req.cookies.get('auth')) { return NextResponse.redirect(new URL('/login', req.url)); } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*'], };Deployment
Build the production version locally:
npm run build npm run startDeploy on Vercel with the Vercel CLI (or by connecting the Git repository in the Vercel dashboard):
npm i -g vercel vercel --prodFor deployment on Cloudflare Workers see Next.js on Cloudflare.
NextJS on Cloudflare Workers
Visit NextJS on Cloudflare Workers page.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
useStateuseStateallows 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
useEffectuseEffectis 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
useMemofor expensive calculations. - ✅ Use
useCallbackto 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.
Anything unclear?
Let us know!