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.

  1. Create an app/ folder, then add a layout.tsx and page.tsx file. These will be rendered when the user visits the root of your application (/).
  2. Create a root layout inside app/layout.tsx with the required <html> and <body> tags:
  3. Finally, create a home page app/page.tsx with some initial content:
    1. Run npm run dev to start the development server.
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 code
  • lib/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:

  1. app/: Your application routes and API routes using the App Router
    • Pages
    • Layouts
    • Route handlers
    • Loading and error states
  2. components/: Reusable UI components
    • Common UI elements (buttons, inputs, cards)
    • Layout components
    • Feature-specific components
    • Consider sub-organizing into:
      • ui/ for basic elements
      • features/ for feature-specific components
      • layout/ for layout components
  3. lib/: Setup and configuration for libraries
    • Database clients
    • Authentication setup
    • External API clients
  4. hooks/: Custom React hooks
    • Form handling hooks
    • Data fetching hooks
    • State management hooks
  5. types/: TypeScript type definitions
    • Shared interfaces
    • Type declarations
    • API response types
  6. services/: API and external service integrations
    • API client functions
    • Third-party service integrations
    • Data transformation layers
  7. utils/: Helper functions
    • Date formatting
    • String manipulation
    • Calculations
    • Validation
  8. context/: React Context providers
    • Theme context
    • Auth context
    • App-wide state management
  9. constants/: Application constants
    • Configuration values
    • Environment variables
    • Static data

Linking and Navigating

<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 main middleware.ts file. This allows for cleaner management of route-specific middleware, aggregated in the middleware.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]

  1. headers from next.config.js
  2. redirects from next.config.js
  3. Middleware (rewritesredirects, etc.)
  4. beforeFiles (rewrites) from next.config.js
  5. Filesystem routes (public/_next/static/pages/app/, etc.)
  6. afterFiles (rewrites) from next.config.js
  7. Dynamic Routes (/blog/[slug])
  8. fallback (rewrites) from next.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