This command sets up everything automatically:
npx create-next-app@latest folderName
Frontend
Next.js uses file-system routing, which means the routes in your application are determined by how you structure your files.
NextJS relies on ‘app router’ based routing.
- Create an
app/
folder, then add alayout.tsx
andpage.tsx
file. These will be rendered when the user visits the root of your application (/
). - Create a root layout inside
app/layout.tsx
with the required<html>
and<body>
tags: - Finally, create a home page
app/page.tsx
with some initial content: -
- Run
npm run dev
to start the development server.
- Run
Defining Routes
Routes start from the main ‘app’ folder. Any nested folders will construct the URL path
Folders are used to define routes. A route is a single path of nested folders, following the file-system hierarchy from the root folder down to a final leaf folder that includes a page.js
file.
Files are used to create UI that is shown for a route segment.
A special page.js
file is used to make route segments publicly accessible.
In this example, the /dashboard/analytics
URL path is not publicly accessible because it does not have a corresponding page.js
file. This folder could be used to store components, stylesheets, images, or other colocated files.
Backend
In Next.js 13+ with App Router, backend api routing works very similarly to frontend routing with folder names inside of src/api
representing the routes as long as they have a route.ts
file in their base.
The key thing to remember is the directory structure determines the API path.
Creating API Routes
- Place your API routes in the
app/api
directory - Each route needs a
route.ts
file - Each route can handle different HTTP methods (GET, POST, etc.)
- Dynamic routes use square brackets (e.g.,
[id]
)
Example:
Let’s say you want to create an API endpoint that is accessible at /api/artworks/create
:
# Directory structure
app/
api/
artworks/
create/
route.ts # This file handles requests
Inside route.ts
, you define the HTTP methods:
import { NextResponse } from 'next/server'
import { prisma } from "@/lib/db"
// This route handles POST requests to /artworks/create
export async function POST(request: Request) {
try {
// Parse the request body
const { title, description, authorId, state } = await request.json();
// Validate input
if (!title || !authorId || !state) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
// Add the artwork to the database using prisma
const newArtwork = await prisma.artwork.create({
data: {
title,
description,
authorId, //adds new artwork to the Artwork table and links it with a user in the User table
state, // need to perform type checking on configuration
likes: 0, // Initialize likes to 0
},
});
return NextResponse.json(newArtwork, { status: 201 });
} catch (error) {
console.error('Error creating artwork:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
[!HTTP METHODS] Each HTTP method (GET, POST, PUT, etc.) must be exported as a separate function
On /api
app/api/*
:
- Contains actual API route handlers
- Runs on the server
- Handles HTTP requests directly
- Uses Next.js API route conventions
- Interacts with your database
- Located in the
app
directory because it’s part of Next.js’s file-based routing system
lib/api/*
:
- Contains functions that CALL your API endpoints
- Can run on client or server
- Provides a clean interface for making API requests
- Handles request formatting and error handling
- Typically uses
fetch
or similar - Could be used anywhere in your application
Think of it this way:
app/api
is your backend server codelib/api
is your frontend API client code
Structuring The Project
This is an example of how you could think about structuring your project:
your-project/
├── docker/
│ └── docker-compose.yml
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ └── artworks/
│ │ │ ├── all/
│ │ │ │ ├── route.ts # GET all artworks
│ │ │ │ └── [id]/
│ │ │ │ └── route.ts # GET single artwork
│ │ │ └── create/
│ │ │ └── route.ts # POST new artwork to db
│ ├── lib/
│ │ ├── db/ # Prisma client and queries
│ │ │ ├── index.ts # Prisma client initialization
│ │ │ └── queries/ # Database operations
│ │ │ └── artwork.ts # Artwork-specific database queries
│ │ ├── utils/
│ │ │ └── middlewares.ts # custom middleware functions
│ │ └── api/
│ │ └── createArtwork.ts # functions that go into API calls
│ ├── components/
│ │ └── ui/
│ │ └── RotatingBoxes.tsx # frontend components
│ ├── types/
│ │ └── index.ts # store all types here
│ └── middleware.ts # default middleware stays at src/
├── prisma/ # Prisma schema migrations stay at root
│ ├── schema.prisma
│ └── migrations/
├── .env
└── package.json
Note: Anything that’s not specific to your application should live in the root (including Docker, Prisma, etc.)
The src/
directory should be used as the primary organisational pattern
src/
app/ # App Router routes and API routes
components/ # Reusable UI components
lib/ # Utility functions, configurations, and libraries
hooks/ # Custom React hooks
types/ # TypeScript type definitions
styles/ # Global styles and CSS modules
context/ # React context providers
services/ # API service functions
constants/ # Constant values and enums
utils/ # Helper functions
Here’s what each directory typically contains:
app/
: Your application routes and API routes using the App Router- Pages
- Layouts
- Route handlers
- Loading and error states
components/
: Reusable UI components- Common UI elements (buttons, inputs, cards)
- Layout components
- Feature-specific components
- Consider sub-organizing into:
ui/
for basic elementsfeatures/
for feature-specific componentslayout/
for layout components
lib/
: Setup and configuration for libraries- Database clients
- Authentication setup
- External API clients
hooks/
: Custom React hooks- Form handling hooks
- Data fetching hooks
- State management hooks
types/
: TypeScript type definitions- Shared interfaces
- Type declarations
- API response types
services/
: API and external service integrations- API client functions
- Third-party service integrations
- Data transformation layers
utils/
: Helper functions- Date formatting
- String manipulation
- Calculations
- Validation
context/
: React Context providers- Theme context
- Auth context
- App-wide state management
constants/
: Application constants- Configuration values
- Environment variables
- Static data
Linking and Navigating
- Using the
<Link>
Component - Using the
useRouter
hook (Client Components) - Using the
redirect
function (Server Components) - Using the native History API
<Link>
is a built-in component that extends the HTML <a>
tag to provide prefetching and client-side navigation between routes. It is the primary and recommended way to navigate between routes in Next.js.
You can use it by importing it from next/link
, and passing a href
prop to the component:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
Authentication
docs: https://nextjs.org/docs/pages/building-your-application/authentication
Clerk
docs: https://clerk.com/docs/quickstarts/nextjs
Middleware
Getting Started
Create a file middleware.ts
(or .js
) in the root of your project to define Middleware. If you’re using the /src
directory, place middleware.ts
in the /src
directory.
[!Organise your middleware in a modular way] While only one
middleware.ts
file is supported per project, you can still organize your middleware logic modularly. Break out middleware functionalities into separate.ts
or.js
files and import them into your mainmiddleware.ts
file. This allows for cleaner management of route-specific middleware, aggregated in themiddleware.ts
for centralized control. By enforcing a single middleware file, it simplifies configuration, prevents potential conflicts, and optimizes performance by avoiding multiple middleware layers.
This is an example of a basic middleware function in NextJs:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
Matching Paths
Because middleware is invoked on every single route in the project by default, it is important to use matchers to precisely target or exclude specific routes.
Matcher
The matcher
config allows you to filter Middleware to run on specific paths.
You can match a single or multiple paths with an array syntax.
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
the matcher supports full regex
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
you can bypass Middleware for certain requests by turning the matcher inputs into objects with source, has and missing:
export const config = {
matcher: [
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
Conditional statements
Conditional statements can also be used to define whether middleware runs
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
[!NextJs Execution Order]
headers
fromnext.config.js
redirects
fromnext.config.js
- Middleware (
rewrites
,redirects
, etc.)beforeFiles
(rewrites
) fromnext.config.js
- Filesystem routes (
public/
,_next/static/
,pages/
,app/
, etc.)afterFiles
(rewrites
) fromnext.config.js
- Dynamic Routes (
/blog/[slug]
)fallback
(rewrites
) fromnext.config.js
Using Cookies
https://nextjs.org/docs/pages/building-your-application/routing/middleware#using-cookies
Setting Headers
https://nextjs.org/docs/pages/building-your-application/routing/middleware#setting-headers
Prisma
Here’s how to set up and use Prisma in your Next.js project. Remember the Prisma folder should go in the root of your project.
PRISMA CLIENT RELOADING
One issue to be aware of with using Prisma in NextJs is how hot reloading effects Prisma Client instance creation. Hot reloading can cause multiple Prisma clients to be created every time we do something leading to excessive db connection limits.
Setup
Installation
npm install prisma @prisma/client
npm install -D prisma
npx prisma init
Generate Schema
# Generate Prisma Client
npx prisma generate
# Run migrations
npx prisma migrate dev --name init
Create a safe PrismaClient generation function
// lib/db.ts
import { PrismaClient } from '@prisma/client'
// A note on global
// you can add ANY properties you want to global!
// It's like a big shared object that you can add stuff to:
// global is a built-in Node object that exists across the entire application and reloads
// we create a typescript type reference for the global object
// TypeScript's type definition for the global object doesn't include our prisma property by default,
// so we need to tell TypeScript "trust us, this will exist".
const globalForPrisma = global as unknown as { prisma: PrismaClient | undefined }
// start with the global object, and make a new variable that points to it
// with particular type information
// step 1: remove all type information with unknown
// step 2: Add new type 'as { prisma: PrismaClient | undefined }'
// This means the global object has a key CALLED 'prisma' that contains a PrismaClient
// the first time this file runs, globalForPrisma is null,
// so a new PrismaClient will be created and assigned to prisma;
// the next time it runs, it will find the existing global object and utilise that
const prisma = globalForPrisma.prisma ?? new PrismaClient() // this is the same as global.prisma
// go into the object globalForPrisma, and look for the prisma key
// assign the value of the key (i.e. the PrismaClient) as a variable prisma OR
// assign a new PrismaClient to the variable prisma
// we assign the prisma client to the global object above
// no hot reloading in prod, so we dont have to worry about it
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export { prisma }
Using Prisma in your API routes
// app/api/users/route.ts
import { prisma } from '@/lib/db'
import { NextResponse } from 'next/server'
export async function GET() {
const users = await prisma.user.findMany()
return NextResponse.json(users)
}
export async function POST(request: Request) {
const data = await request.json()
const user = await prisma.user.create({
data: {
email: data.email,
name: data.name
}
})
return NextResponse.json(user)
}
[!Important Notes] The schema file must be in prisma/schema.prisma. Make sure your db information is in the .env file