front-end react framework that enables server-side rendering and static site generation
-
main features:
- routing: how users navigate between different parts of your application
- route: URL path that maps to a page route or to an api route
- file-based routing
- every file inside
app/is a route (app/index.tsx=>/) - dynamic routes:
app/workout/[id].tsx=>/workout/:id - route vs endpoint:
- route: a page in your website (like
/aboutor/products)- what users see and interact with in the browser
- example:
/dashboardshows your dashboard page
- endpoint: where your API receives and sends data
- handles data operations in the background
- example:
/api/usersmight get or update user data
- route: a page in your website (like
- Server-side Rendering (SSR) vs Static Site Generation (SSG)
- use
getServerSidePropsfor dynamic, always-updated data - use
getStaticPropsfor pre-generated pages (faster)
- use
- typescript support
- allows for optimization of images and assets
- streaming: data transfer technique that breaks down a route into smaller chunks and progressively transfers them to the client
- prevents slow data request from blocking the entire page
- routing: how users navigate between different parts of your application
-
basic
npmcommands:- update dependencies:
npm update - only update next.js, react.js and react-dom:
npm install next@latest react@latest react-dom@latest - run next.js application in development mode:
npm run dev - start production server:
npm run start - run tests:
npm test
- update dependencies:
setting scripts in package.json:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}- with this
package.json:- view application by running dev script:
npm run dev- by default, next.js uses
http://localhost:3000
- by default, next.js uses
- to run build script:
npm run build - to run start script:
npm run start - to run lint script:
npm run lint
- view application by running dev script:
- create and clone github repository
- initialize a new Next.js application in the current directory:
npx create-next-app@latest . - answer the questions
- would you like to use TypeScript? Yes
- would you like to use eslint? Yes
- would you like to use Tailwind CSS? Yes
- would you like to use src/ directory? Yes
- would you like to use App Router? Recommended Yes
- would you like to use Turbopack for
next dev? › Yes - would you like to customize the default import alias (@/*)? No
- wait for the installation to finish
npm run devto start the development server- start editing
src/pages/index.tsxto see the changes in the browser - install basic dependencies
npm install <dependency>-
clsx: utility to conditionally apply css classes -
jsonwebtoken: create and verify tokens -
bcrypt: password hashing -
pg: postgresql client -
cookies-next: cookie management -
tailwind-merge: merge tailwind classes without style conflicts -
if auth provider is
better-auth:lucide-react: customizable iconstailwindcss-animate: animations for tailwindsonner: sound effectsnpx shadcn@latest init -dsee the documentation before adding: beautiful components
-
if auth provider is
next-auth@beta:npx auth secret: create a secret key
-
if auth provider is
@clerk/nextjs:@clerk/nextjs
next.js uses a file-system based routing system
my-app/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ └── items/
│ │ │ ├── route.ts # Handles GET, POST for /api/items
│ │ │ └── [id]/
│ │ │ └── route.ts # Handles GET, PUT, DELETE for /api/items/:id
│ ├── lib/
│ │ └── db.ts # DB mock or DB logic
├── next.config.js
├── package.json
├── tsconfig.json
src/: optional application source foldersrc/app/(new and recommended): app router, containsapi(back end) and each page directory- endpoint vs route: you can have different endpoints for the same route
- endpoint: where your API receives and sends data
- e.g.
GET localhost:8080/employees/42,DELETE localhost:8080/employees/42
- e.g.
- route: URL that maps to a page in your website (like
/aboutor/products)- e.g.
localhost:8080/employees/42
- e.g.
- endpoint: where your API receives and sends data
- uses nested folders to define routes
src/app/page.tsx: default page for root route (/)- equivalent to
index.tsxin the pages router (src/pages/)
- equivalent to
src/app/[route]/page.tsx: page component,[route]defines the route and contains the front end- e.g.
src/app/products/page.tsx
- e.g.
src/app/[route]/layout.tsx: layout component- layout: ui that is shared between multiple pages
/app/[route]/loading.tsx: loading indicator or a fallback ui while a specific route segment is loading- route segment: portions of the url path that corresponds to a directory or file within the
appdirectory <Suspense>component: wraps the loading component- use
fallback={<p>Loading...</p>}prop to handle cases where a page's content isn't available at build time
- use
- route segment: portions of the url path that corresponds to a directory or file within the
src/app/lib/: contains functions used in the application- e.g. reusable utility functions and data fetching functions
src/app/api/: stores all api routes- e.g.
src/app/api/users/route.ts: contains all user api routes
- e.g.
src/app/ui/: contains all ui components for the application
- endpoint vs route: you can have different endpoints for the same route
src/pages/(old, not recommended): pages router, server-side- files directly represent routes
public/: static assets such as images, fonts, etc- can be referenced by your code starting from base URL (
/)
- can be referenced by your code starting from base URL (
- top-level files:
next.config.js: config file for next.js application- customize build and runtime behaviour (e.g. enable experimental features, modify webpack configurations, etc)
package.json:middleware.ts:.env: file that stores environment variables at the root of the project- examples of env vars:
DATABASE_HOST: database credentials for connection poolDATABASE_NAME: database credentials for connection poolDATABASE_USER: database credentials for connection poolDATABASE_PASSWORD: database credentials for connection poolGOOSE_DBSTRING: allows database migration with gooseGOOSE_DRIVER: allows database migration with gooseAUTH_SECRET: used to encrypt session tokens in Auth.jsNEXTAUTH_URL: sets the canonical base URL of your application (e.g.pesodevops.com)- used for OAuth callbacks, redirect URLs, email links and CSRF protection
AUTH_TRUST_HOST: when set totrue, allows Auth.js to trust theNEXTAUTH_URLin production if it's notlocalhost- used for deployment
AUTH_GOOGLE_ID: enables OAuth/OIDC authenticationAUTH_GOOGLE_SECRET: enables OAuth/OIDC authenticationAWS_ACCESS_KEY_ID: aws credentials for terraformAWS_SECRET_ACCESS_KEY: aws credentials for terraformECR_REPO_URLEC2_PUBLIC_IP
- if the same variable is defined in multiple
.envfiles (.env.local,.env.production,.env.development), the final value will be the one in the file with highest priority .env.local: highest priority.env.productionand.env.development: medium priority.env: lowest priority
- examples of env vars:
.env.developent(recommended at the beggining): developent environment variables.env.production(use for production): production environment variableseslintrc.json: config file for eslinttsconfig.json: config file for typescriptjsconfig.json: config file for javascript
- routing files:
layout.(js/ts/tsx): shared UI that wraps multiple pages (like headers/navigation)page.(js/ts/tsx): the actual page content and UI for a routeloading.(js/ts/tsx): loading state shown while page content loadsnot-found.(js/ts/tsx): custom 404 page when a route doesn't existerror.(js/ts/tsx): error UI shown when a page has an errorglobal-error.(js/ts/tsx): error UI for errors outside pagesroute.(js/ts/tsx): API endpoints and request handlerstemplate.(js/ts/tsx): similar to layout but creates new instance on navigationdefault.(js/ts/tsx): fallback page for parallel routes
Warning
routes are not accessible until a page.(js/ts/tsx) or route.(js/ts/tsx) is added to the route segment*
Tip
try to use app folder purely for routing purposes
- separate
clientandserverfolders vs single next.js application handling both frontend and api routes (recommended)- separate
clientandserverfolders:- advantages
- separation of concerts
- independent development
- advantages
- single next.js application handling both frontend and api routes (recommended)
- advantages
- simplicity: no need to manage separate express server
- integrated routing: next.js handles both page and api routing
- advantages
- separate
organize routes into groups e.g. by site section, intent or team
- created by wrapping a folder in parenthesis:
(foldername)- examples:
src/app/(admin)/dashboard/page.tsfile path =>/dashboardURL pathsrc/app/(marketing)/about/page.tsfile path =>/aboutURL pathsrc/app/(marketing)/blog/page.tsfile path =>/blogURL path
- examples:
- root layout (
layout.tsx): wraps all pages- contains persistent components (e.g. navbar, footer)
- page components (
page.tsx): represents individual pages (e.g.,/workouts,/workouts/[id])- fetches data and passes it down to child components
- ui components (
components/): reusable components
- top-down (props): parent components pass data down to child components through props
- bottom-up (callbacks/state management): child components communicate with parents through callback functions or state management libraries like Redux
- use
<Link href="/users"></Link>tag instead of<a href="/users"></a>to optimize page navigation import Link from "next/link";href(Hypertext REFerence): destination URL/path that the link will navigate to
- create an array of navigation links
const links = [
{ name: "Home", href: "/dashboard", icon: HomeIcon },
{
name: "Invoices",
href: "/dashboard/invoices",
icon: DocumentDuplicateIcon,
},
{ name: "Customers", href: "/dashboard/customers", icon: UserGroupIcon },
];- use
mapto create a link for each
<>
{links.map((link) => {
const LinkIcon = link.icon;
return (
<Link
key={link.name}
href={link.href}
className={clsx(
"flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3",
{
"bg-sky-100 text-blue-600": pathname === link.href,
},
)}
>
<LinkIcon className="w-6" />
<p className="hidden md:block">{link.name}</p>
</Link>
);
})}
</>-
Tailwind CSS classes explanation (
hidden md:block):hidden: element is not visible by default (display: none)md:block: on medium screens (768px and up), element becomes visible (display: block)- on medium screens and up: text is visible
-
clsx: utility to conditionally apply css classesclsx('<css classes 1>', {'<css classes 2>': pathname === link.href,})'<css classes 1>': css classes 1 that are always applied'<css classes 2>': if the condition is true (pathname === link.href), the css classes 2 are applied
more examples:
// Conditional classes
clsx("button", isActive && "active");
// If isActive is true: "button active"
// If isActive is false: "button"- route handlers are defined in a
route.js|tsfile inside theappdirectory- there cannot be a
route.js|tsfile at the same route segment level aspage.js|ts - not cached by default
- each request triggers a fresh database query, rather than serving a stored (cached) response
- there cannot be a
app/
blog/
page.js ❌
route.js ❌
app/
blog/
page.js ✅ (handles the UI)
api/
route.js ✅ (handles API requests)
| page | route | result |
|---|---|---|
app/page.ts |
app/route.ts |
❌ conflict |
app/page.ts |
app/api/route.ts |
✅ valid |
app/[user]/page.ts |
app/api/route.ts |
✅ valid |
- each
route.tsorpage.tsfile must handle all http verbs for that route- i.e. you can't have some http methods handled in one file and others in another file for the same route
a route file allows you to create custom request handlers for a given route
import type { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const url = request.nextUrl;
return new Response(`URL: ${url}`, { status: 200 });
}
export async function POST(request: NextRequest) {
const body = await request.json();
return new Response(`Received: ${JSON.stringify(body)}`, { status: 200 });
}
export async function PUT(request: NextRequest) {
const body = await request.json();
return new Response(`Updated: ${JSON.stringify(body)}`, { status: 200 });
}
export async function DELETE(request: NextRequest) {
const body = await request.json();
return new Response(`Deleted: ${JSON.stringify(body)}`, { status: 200 });
}NextRequest: object that gives access to request information like headers, cookies, URL parameters, and body datarequest.nextUrl: URL's request
request.json(): This method parses the request body as JSON- must be used inside an
asyncfunction
- must be used inside an
NextResponse: object that allows you to create custom responses (e.g. status code, headers, etc)return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
example:
return new Response('Hello', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
})next.js has come client-side hooks that allow you to interact with the browser
useSearchParams: convert url parameters of the current url to jsonusePathName: get current URL's pathnameuseRouter: hook that enables client-side navigation and access to router information (current route, query parameters, etc)useSession: hook that provides access to the current user's session data (e.g. user id, email, etc)- available only client-side
-
components in the
app/folder are server components by default -
"use client": add it to the top of a file before allimportstatements to convert component into a client component- required to use client-side react features, such as
useState,useEffect, event handlers, etc - marks a file and all its dependencies as part of the client bundle
- all child components and imports are also considered client components
- required to use client-side react features, such as
-
"use server": marks all exported functions as server actions- enables "server actions"
- server actions: server functions that can be called directly from client components
- enables "server actions"
Tip
a project usually uses a mix of both types try to maximize the use of server components and minimize client components
-
client-side rendering is the most common way to render a web page
-
the browser downloads a minimal HTML page, then downloads and executes JavaScript that renders the page
-
the JavaScript can make requests to the server to fetch data and update the page
-
client component: components that are rendered in the user's browser
- bigger payload size
-
server-side rendering is when the server generates the HTML for the page and sends it to the browser
-
the browser receives a fully rendered page, which can be faster to load and better for SEO
-
server-side rendering can be more complex to set up and maintain
-
server component (default): components that are rendered in the server
- smaller payload size
- improves SEO (Search Engine Optimization)
- cannot
- listen to browser events (e.g. click, change, submit, etc)
- access browser apis (e.g. localstorage)
- maintain state
- use effects
pre-render pages at build time for performance
- build time: the time when your application is being built and prepared for deployment
- Next.js pre-renders your pages to HTML during this phase
- each page is generated once at build time and reused on each request
- this allows for very fast page loads and SEO benefits
- server can serve static files instead of executing server-side code for each request
to use SSG, you export an async function called getStaticProps in pages:
// pages/index.js
export async function getStaticProps() {
return {
props: {
message: 'Hello from static generation!'
}
};
}
export default function Home({ message }) {
return <h1>{message}</h1>;
}- static rendering: render at build time
- use case: UI with no data OR with data shared across users
- faster
- easier caching
- reduce server load
- better SEO
- dynamic rendering: render page in response to a user's request
- use case: all cases that don't fit static rendering
- user-specific content
- real-time data
- use case: all cases that don't fit static rendering
- partial prerendering: combination of the benefits of static and dynamic rendering in the same route
- use case: pages that have some content that can be pre-rendered at build time and some dynamically generated content
- route segment: portions of the url path that corresponds to a directory or file within the
appdirectory - segments: each part of the url path separated by a forward slash (
/) - dynamic segments (like
[id]): create routes that handle variable parts of the url - hidration: process of making server-rendered html interactive on the client-side
- login to get a session token
- inspect => application => cookies => copy authjs.session-token
- use the token in the request to test the api route
curl -X POST http://localhost:3000/api/exercises \
-H "Content-Type: application/json" \
-H "Cookie: authjs.session-token=<your-session-token>" \
-d '{
"name": "Push-up",
"description": "A basic exercise for upper body strength"
}'- how do i make http requests?
- just create the routes in the
appfolder - example
- just create the routes in the
- Should i use express.js or does next.js have a built-in solution for this?
- no need to install express.js
- Next.js has a built-in API routing system that can replace express.js
- what is the best method to fetch data from the database?
- in my opinion, the best way to fetch data is to use
pg, without any ORM
- in my opinion, the best way to fetch data is to use
- if i have a route for a resource, how do i use the routing system to create endpoints for a specific resource (using the id)?
- should all endpoints be in one file?
- no, each resource (
/workouts) has a folder, and the id-based routes (/workouts/:id) go inside[id].tsfile route.ts: handle GET all and POST new[id].ts: handle GET 1, PUT 1 and DELETE 1
- no, each resource (
- should all endpoints be in one file?
app/
api/
workouts/
route.ts → Handles `/api/workouts` (GET all, POST new)
[id].ts → Handles `/api/workouts/:id` (GET, PUT, DELETE)
- ensure database connection is always released
- problem: if an error occurs before client.release(), the connection is not released
- fix: use a try-finally block
- Use Secure HTTP-Only Cookies for Tokens
- Problem: Tokens are sent in JSON response, making them vulnerable to XSS
- Fix: Use
NextResponse.cookies.set()for storing tokens securely
- Return Only the Access Token in Response
- Why? Refresh tokens should not be sent to the frontend. They should be stored in HTTP-only cookies