Final Year Project

Full Stack Web Dev
Student Handbook

A practical, ground-up guide to building and deploying modern web applications — covering everything from Git basics to production deployments on Vercel and Railway.

📚 14 topics covered
Focused on Next.js webapp stack
🇲🇾 Malaysia-context tips included
Recommended Learning Order
1
Git / GitHub
2
HTML·CSS·JS
3
React
4
Next.js
5
Backend + DB
6
Deploy
🛠️
00 — Setup First

Dev Tools You Must Install

Editor
VS Code
The standard editor for web dev. Free, fast, and has extensions for everything. Install from code.visualstudio.com.
Runtime
Node.js (LTS)
Required to run JavaScript on your machine and install npm packages. Always pick the LTS (Long-Term Support) version.
Version Control
Git
The command-line tool behind GitHub. Comes pre-installed on Mac/Linux; on Windows install from git-scm.com.
API Testing
Postman / Thunder Client
Test your backend APIs without a frontend. Thunder Client is a VS Code extension (easier). Postman is a standalone app.

Essential VS Code Extensions

ESLint Prettier Tailwind CSS IntelliSense GitLens Thunder Client Auto Rename Tag Path Intellisense
🐙
01 — Learn This First

Git & GitHub

Git is a tool that tracks changes to your code over time. GitHub is a website that hosts your code (repositories) online. Think of Git like a save system for your project — except it saves the full history, and you can roll back to any point.

💡 Your FYP supervisor will almost certainly ask you to share a GitHub repo. Start committing from day one — an active commit history shows your work progress.

Core Git Concepts

Term What It Means Analogy
Repository (repo) Your project folder tracked by Git The entire project folder
Commit A saved snapshot of your code at a point in time Hitting "Save" in a game
Branch A parallel copy of your code to work on without affecting main A draft copy of a document
Merge Combining two branches back together Accepting changes from a draft into the final doc
Push Upload your local commits to GitHub Uploading to Google Drive
Pull Download latest commits from GitHub to local Syncing from Google Drive
Clone Download a GitHub repo to your machine for the first time Downloading a project folder
.gitignore File listing paths Git should NOT track A "don't save this" list

The Daily Git Workflow

1
Check what changed

Before anything, see what files you've modified.

2
Stage your changes

Tell Git which files to include in the next commit.

3
Write a commit message

Describe what you did clearly. Future-you will thank you.

4
Push to GitHub

Upload your commits so they're backed up and shareable.

Everyday commands bash
# ── First-time setup ───────────────────────────────────
git config --global user.name  "Your Name"
git config --global user.email "you@email.com"

# ── Start a new project ────────────────────────────────
git init                         # initialise git in current folder
git remote add origin https://github.com/you/repo.git

# ── Clone an existing repo ─────────────────────────────
git clone https://github.com/you/repo.git

# ── Daily workflow ─────────────────────────────────────
git status                       # see changed files
git add .                        # stage everything
git add src/app/page.tsx         # or stage a specific file
git commit -m "feat: add login page"
git push origin main             # push to GitHub

# ── Branches ───────────────────────────────────────────
git checkout -b feature/dashboard  # create and switch to new branch
git checkout main                  # go back to main
git merge feature/dashboard        # merge feature into main

# ── Undo mistakes ──────────────────────────────────────
git restore filename.tsx           # discard uncommitted changes to a file
git reset --soft HEAD~1            # undo last commit, keep changes staged

Commit Message Convention

Use a prefix to describe what type of change you made. This makes your history readable at a glance.

Prefix When to use Example
feat: New feature or functionality feat: add user registration
fix: Bug fix fix: login form not submitting
chore: Config, deps, setup — no logic change chore: install tailwindcss
style: UI/CSS changes only style: update hero section padding
refactor: Code cleanup without changing behavior refactor: extract fetchUser helper
docs: README or documentation changes docs: update setup instructions

What Goes in .gitignore

.gitignore — never commit these gitignore
# Dependencies
node_modules/

# Environment secrets
.env
.env.local
.env.production

# Build outputs
.next/
dist/
build/

# OS files
.DS_Store
Thumbs.db

# Editor files
.vscode/settings.json
🚨 NEVER commit .env files. They contain secret keys. If you accidentally push secrets to GitHub, rotate them immediately — bots scan GitHub for exposed keys within minutes.
🌐
02 — The Foundation

HTML / CSS / JavaScript

Every website ultimately runs HTML, CSS, and JavaScript in the browser. Frameworks like React and Next.js are just tools that help you write and organise these three things more efficiently. You don't need to master vanilla JS, but you need to understand the concepts.

HTML — Structure

HTML defines what is on the page — headings, paragraphs, buttons, images, links.

<!-- HTML = content structure -->
<div class="card">
  <h2>Patient Dashboard</h2>
  <p>Heart rate: <span id="bpm">72</span> BPM</p>
  <button onclick="refresh()">Refresh</button>
</div>

CSS — Styling

CSS defines how things look — colours, fonts, layout, spacing, animations. In a Next.js project with Tailwind you write CSS as utility classes rather than separate CSS files.

/* Traditional CSS */
.card {
  background: #ffffff;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

/* Tailwind equivalent (in JSX className) */
// className="bg-white rounded-xl p-6 shadow-lg"

JavaScript — Behaviour

JS makes the page interactive and handles data. Learn these fundamentals before React:

Concept Why it matters
Variablesconst , let Store and update data
Functions & arrow functions Reusable blocks of logic — used constantly in React
Arrays & objects How JSON data (from APIs) is structured
Array methods.map() , .filter() , .find() Used in React to render lists, filter data
Async / await & fetch() Calling your backend API from the frontend
Destructuring Unpacking object properties — very common in React
Spread operator (...) Merging/copying objects and arrays
Template literals Building strings with variables: `Hello ${name}`
Free resources: javascript.info (best JS reference), MDN Web Docs (official), The Odin Project (structured curriculum).
⚛️
03 — Component-Based UI

React

React is a JavaScript library made by Meta for building UIs out of reusable components . Instead of directly manipulating the HTML, you describe what the UI should look like, and React updates the DOM efficiently.

Core Concepts You Must Know

1. Components

Everything in React is a component — a JavaScript function that returns JSX (HTML-like syntax).

// A simple React component
function GaugeCard({ label, value, unit, status }) {
  return (
    <div className=`card ${status}`>
      <span className="label">{label}</span>
      <span className="value">{value} {unit}</span>
    </div>
  );
}

// Using it — pass data as props
<GaugeCard label="Heart Rate" value=72 unit="BPM" status="normal" />

2. Props

Props (properties) are how you pass data into a component. They flow one-way: parent → child. You cannot modify props inside the component.

3. State — useState

State is data that lives inside a component and can change. When state changes, React re-renders the component automatically.

import { useState } from "react";

function Counter() {
  // [currentValue, functionToUpdateIt] = useState(initialValue)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

4. useEffect — Side Effects

Used for anything that happens outside React's render cycle: fetching data, setting up timers, subscribing to events.

import { useState, useEffect } from "react";

function LiveDashboard() {
  const [reading, setReading] = useState(null);

  useEffect(() => {
    // This runs after the component renders
    const fetchData = async () => {
    const res = await fetch("/api/readings/latest");
      const data = await res.json();
      setReading(data);
    };

    fetchData();                          // fetch immediately
    const interval = setInterval(fetchData, 5000); // then every 5s
    return () => clearInterval(interval); // cleanup on unmount
  }, []);                                 // [] = run once on mount

  if (!reading) return <p>Loading...</p>;
  return <p>BPM: {reading.bpm}</p>;
}

5. Conditional Rendering & Lists

// Conditional rendering
{isLoading ? <Spinner /> : <Dashboard />}
{error && <ErrorBanner message={error} />}

// Rendering a list
{alerts.map((alert) => (
  <AlertRow key={alert.id} data={alert} />
))}
⚠️ Always add a key prop when rendering lists. It helps React efficiently update items. Use a unique ID from your data, not the array index.
04 — The Main Framework

Next.js

Next.js is a framework built on top of React that adds: file-based routing, server-side rendering, API routes, image optimisation, and more. It's what most modern web apps use for FYPs because it handles both frontend and lightweight backend in one codebase.

📌 Use the App Router (introduced in Next.js 13). All new projects should use this — the older "Pages Router" still exists but is being phased out.

Folder Structure (App Router)

my-app/
├── app/
│   ├── layout.tsx          # Root layout — wraps ALL pages
│   ├── page.tsx            # Home page  →  /
│   ├── about/
│   │   └── page.tsx        # About page →  /about
│   ├── dashboard/
│   │   ├── page.tsx        # Dashboard  →  /dashboard
│   │   └── loading.tsx     # Auto loading state
│   └── api/
│       └── readings/
│           └── route.ts    # API endpoint →  /api/readings
├── components/             # Reusable UI components
├── lib/                    # Utilities, db clients, helpers
├── public/                 # Static files (images, fonts)
├── .env.local              # Secret environment variables
└── next.config.ts

File Routing Rules

File Path URL Notes
app/page.tsx / Home page
app/about/page.tsx /about Static route
app/blog/[slug]/page.tsx /blog/my-post Dynamic route — slug is a param
app/api/users/route.ts /api/users API endpoint
app/layout.tsx Wraps all pages Add nav, footer, providers here
app/loading.tsx Shown while page loads Automatic — just create the file
app/not-found.tsx 404 page Shown for unmatched routes

Server vs Client Components

This is the most important Next.js concept to understand. By default, every component is a Server Component — it runs only on the server. To use React hooks ( useState , useEffect ) you need a Client Component .

Feature Server Component Client Component
Runs on Server only Browser (after hydration)
Can use useState / useEffect ❌ No ✅ Yes
Can directly query DB ✅ Yes ❌ No
SEO-friendly ✅ Yes Partial
How to make it Default (do nothing) Add "use client" at top of file
// ── Server Component (default) ───────────────────────
// Can fetch DB data directly. No "use client" needed.
async function ProductsPage() {
  const products = await db.query("SELECT * FROM products");
  return <ProductList items={products} />;
}

// ── Client Component ─────────────────────────────────
"use client";   // This directive MUST be the first line

import { useState } from "react";

export function SearchBar() {
  const [query, setQuery] = useState("");
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

API Routes

Next.js can serve backend API endpoints without a separate server. These live in app/api/*/route.ts .

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

// GET /api/users
export async function GET() {
  const users = await fetchUsersFromDB();
  return NextResponse.json(users);
}

// POST /api/users
export async function POST(req: NextRequest) {
  const body = await req.json();
  const newUser = await createUser(body);
  return NextResponse.json(newUser, { status: 201 });
}

Getting Started

# Create a new Next.js project
npx create-next-app@latest my-fyp-app

# Options to select during setup:
# ✅ TypeScript  ✅ Tailwind CSS  ✅ App Router  ✅ ESLint

cd my-fyp-app
npm run dev          # starts dev server at http://localhost:3000
🎨
05 — Styling

Tailwind CSS

Tailwind is a CSS framework where instead of writing .card { padding: 24px; } in a separate file, you apply pre-built utility classes directly in your HTML/JSX. It's faster for rapid development and pairs perfectly with Next.js.

Core Utility Classes to Know

Category Examples What They Do
Spacing p-4 px-6 mt-2 gap-4 Padding, margin, gap (multiples of 4px)
Layout flex grid items-center justify-between Flexbox and grid layout
Sizing w-full h-screen max-w-xl Width, height, max-width
Typography text-xl font-bold text-gray-500 Font size, weight, colour
Colour bg-white text-blue-600 border-gray-200 Background, text, border colours
Borders border rounded-xl shadow-md Border, border-radius, shadows
Responsive md:flex lg:grid-cols-3 Apply class only at certain breakpoints
Hover/Focus hover:bg-blue-700 focus:outline-none State-based styling
// A styled card component with Tailwind
function ReadingCard({ label, value, color }) {
  return (
    <div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
      <p className="text-sm text-gray-500 uppercase tracking-wide font-medium">
        {label}
      </p>
      <p className=`text-4xl font-bold mt-2 ${color}`>
        {value}
      </p>
    </div>
  );
}
💡 Install the Tailwind CSS IntelliSense extension in VS Code. It gives you autocomplete for every utility class and shows the actual CSS it generates on hover.
⚙️
06 — Server Side

Backend Concepts

The backend is the part of your app users never see. It's a server that receives requests, runs business logic, talks to a database, and sends responses. Your FYP may have a separate backend (Python/FastAPI, Node/Express) or use Next.js API routes — both are valid.

Backend Frameworks by Language

JavaScript / TypeScript
Next.js API Routes
Built-in to Next.js. Best choice if your frontend is also Next.js — one codebase, one deploy.
JavaScript
Express.js
The classic Node.js framework. Minimal and flexible. Good for more complex separate backends.
Python
FastAPI
Fast, modern Python framework with automatic docs. Great for AI/ML projects where Python is needed for models.
Python
Django
Batteries-included Python framework. Has a built-in admin panel, auth, and ORM — good for data-heavy apps.

Key Backend Concepts

Authentication vs Authorisation

Term Meaning Example
Authentication "Who are you?" — verifying identity Login with email + password
Authorisation "What can you do?" — permission check Only admins can delete records
JWT JSON Web Token — a signed token proving who you are Stored in cookie/localStorage after login
Session Server-side record of who is logged in Alternative to JWTs

Middleware

Functions that run between the request arriving and your route handler running. Common uses: checking auth headers, logging, rate limiting, CORS.

CORS (Cross-Origin Resource Sharing)

A browser security rule. If your frontend is on myapp.vercel.app and your backend is on api.railway.app , the browser blocks requests by default. You must configure your backend to allow your frontend's origin.

# FastAPI CORS example
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.vercel.app"],  # your Vercel URL
    allow_methods=["*"],
    allow_headers=["*"],
)
🔌
07 — Communication Layer

REST API Design

REST (Representational State Transfer) is a set of conventions for designing HTTP APIs. Your frontend calls these URLs to get or change data. The rules are simple but important.

HTTP Methods

Method Action Example
GET Read / fetch data GET /api/users — get all users
POST Create new data POST /api/users — create a user
PUT Replace / update (full) PUT /api/users/5 — replace user 5
PATCH Partial update PATCH /api/users/5 — update one field
DELETE Delete data DELETE /api/users/5 — delete user 5

HTTP Status Codes You Must Know

Code Name When to use
200 OK Successful GET, PUT, PATCH, DELETE
201 Created Successful POST that created a record
400 Bad Request Invalid input or malformed request body
401 Unauthorized No valid auth token provided
403 Forbidden Authenticated but not allowed to do this
404 Not Found Resource doesn't exist
422 Unprocessable Entity Validation failed (FastAPI uses this)
500 Internal Server Error Something crashed on the server

Calling an API from Next.js (frontend)

// Using fetch() to call your backend
async function getReadings() {
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/readings/latest`);

  if (!res.ok) {
    throw new Error("Failed to fetch readings");
  }

  return res.json();
}

// POST example
async function createReading(data) {
  const res = await fetch("/api/readings", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return res.json();
}
💡 Consider using TanStack Query (React Query) or SWR instead of raw fetch in useEffect . They handle loading/error states, caching, and refetching automatically.
🗄️
08 — Data Persistence

SQL & PostgreSQL

A database stores your app's data permanently. PostgreSQL (often called Postgres) is the most popular open-source relational database and the one Supabase uses under the hood.

Core SQL Commands

-- Create a table
CREATE TABLE users (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email       TEXT NOT NULL UNIQUE,
  name        TEXT,
  created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- Insert a row
INSERT INTO users (email, name) VALUES ('ali@example.com', 'Ali');

-- Query rows
SELECT * FROM users;
SELECT email, name FROM users WHERE name = 'Ali';
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;

-- Update
UPDATE users SET name = 'Ahmad' WHERE id = 'some-uuid';

-- Delete
DELETE FROM users WHERE id = 'some-uuid';

-- JOIN — combine data from two tables
SELECT orders.id, users.name, orders.total
FROM orders
INNER JOIN users ON orders.user_id = users.id;

ORMs — Avoiding Raw SQL

An ORM (Object-Relational Mapper) lets you interact with your database using your language's syntax instead of SQL strings. Strongly recommended for FYPs.

ORM Language Notes
Prisma TypeScript/JS Best TypeScript ORM. Auto-generates types. Use with Next.js.
Drizzle TypeScript/JS Lighter than Prisma, more SQL-like syntax. Good for edge deployments.
SQLAlchemy Python The standard Python ORM. Used with FastAPI/Django backends.
// Prisma example — query users in TypeScript
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

// Same as: SELECT * FROM users ORDER BY created_at DESC LIMIT 10
const users = await prisma.user.findMany({
  orderBy: { created_at: "desc" },
  take: 10,
});

Important Concepts

Concept Explanation
Primary Key A unique identifier for each row (usually id ). Use UUID, not integer, for distributed systems.
Foreign Key A column that references the primary key of another table. Enforces relationships.
Index Makes queries on a column much faster. Always index columns you filter or sort by.
Migration A script that changes your DB schema (adding columns, tables). Track these in version control.
Transactions Group multiple DB operations so they all succeed or all fail together (atomic).
🟩
09 — Managed Database

Supabase

Supabase gives you a hosted PostgreSQL database with a web dashboard, automatic REST API, authentication, file storage, and realtime subscriptions — all without managing your own server. It's the fastest way to add a database to a Next.js FYP.

What Supabase Gives You

Database
PostgreSQL
Full PostgreSQL. Edit tables in a GUI, run SQL queries, set up migrations — all from the browser.
Auth
Built-in Auth
Email/password login, magic links, Google/GitHub OAuth — all configured with a few clicks. No code needed for basic auth.
Realtime
Live Updates
Subscribe to database changes in real-time. Great for dashboards, live feeds, and notifications.
Storage
File Storage
Upload and serve images, PDFs, and other files. Each bucket can have its own access rules.

Setup Steps

1
Create a project at supabase.com

Sign up → New Project → Choose Singapore region (closest to Malaysia). Note the database password — you can't recover it.

2
Create your schema

Go to SQL Editor → paste your CREATE TABLE SQL → run it. Or use the Table Editor GUI.

3
Get your connection strings

Settings → Database. You need: SUPABASE_URL , SUPABASE_ANON_KEY , and the DATABASE_URL for direct Postgres access.

4
Install Supabase client

In your Next.js project: npm install @supabase/supabase-js . Then initialise the client with your env variables.

// lib/supabase.ts — initialise the client
import { createClient } from "@supabase/supabase-js";

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Query data from a component
const { data, error } = await supabase
  .from("readings")
  .select("*")
  .order("recorded_at", { ascending: false })
  .limit(10);

// Insert data
const { data, error } = await supabase
  .from("readings")
  .insert({ spo2: 97.5, bpm: 72, temperature: 36.6 });

// Realtime subscription
supabase
  .channel("readings")
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "readings" },
    (payload) => console.log("New reading:", payload.new))
  .subscribe();
⚠️ Enable Row Level Security (RLS) on your tables in production. Without it, anyone with your anon key can read and write all your data. Supabase has a guide for this in their docs.

Supabase Free Tier Limits (as of 2025)

Resource Free Tier Notes
Database size 500 MB Enough for most FYPs
Auth users 50,000 More than enough
Storage 1 GB For file uploads
Projects 2 active Pause inactive ones to have more
Pausing After 1 week inactive Send a query weekly to keep it alive
10 — Frontend Hosting

Vercel

Vercel is the company that made Next.js, and their platform is specifically optimised for it. Connecting your GitHub repo to Vercel gives you automatic deployments — every push to main deploys your app automatically.

How Vercel Works

1
Connect GitHub repo

Sign up at vercel.com with GitHub → New Project → Import your repo. Vercel auto-detects Next.js.

2
Set environment variables

In Vercel Project Settings → Environment Variables. Add NEXT_PUBLIC_SUPABASE_URL , NEXT_PUBLIC_SUPABASE_ANON_KEY , and any other secrets.

3
Deploy automatically

Every git push to your main branch triggers a new deployment. Feature branches get preview URLs.

4
Add custom domain

Project Settings → Domains → Add your domain from Hostinger. Vercel gives you instructions to update DNS.

Key Features

Feature What It Does
Preview Deployments Every pull request / branch gets its own unique URL. Share with supervisors for review.
Edge Network (CDN) Your static assets are served from the closest server to the user globally.
Analytics Built-in Web Vitals monitoring (page load speed, etc.)
Environment Variables Separate variables for Production, Preview, and Development environments.
Logs See server logs (from API routes) in real-time in the Vercel dashboard.
Vercel's free (Hobby) plan is sufficient for FYPs. It includes unlimited deployments, 100GB bandwidth/month, and lets you add a custom domain.

Common Gotchas

⚠️ Build fails on Vercel but works locally? 99% of the time it's a missing environment variable. Check that all your .env.local variables are added in Vercel's project settings.
⚠️ TypeScript errors on deploy: Vercel runs a full TypeScript type-check on build. Fix all TS errors locally first with npm run build before pushing.
🚂
11 — Backend Hosting

Railway

Railway hosts your backend servers. Unlike Vercel (which is optimised for static-first Next.js frontends), Railway runs persistent servers — which you need for Python FastAPI backends, background jobs, or any long-running process.

When to Use Railway vs Vercel

Use Vercel for Use Railway for
Next.js frontend Python FastAPI / Django backend
Next.js API routes (serverless) Express.js server
Static sites Background workers, cron jobs
Edge functions Anything that needs persistent state in memory

Deploying a FastAPI backend to Railway

1
Create railway.toml in your backend folder

This tells Railway how to build and start your app.

2
Connect your GitHub repo

railway.app → New Project → Deploy from GitHub. Select repo and subfolder if it's a monorepo.

3
Set environment variables

Variables tab in Railway dashboard → add DATABASE_URL, SECRET_KEY, etc.

4
Railway generates a public URL

Copy this URL and paste it as NEXT_PUBLIC_API_URL in Vercel's settings.

# railway.toml — place in your backend/ folder
[build]
builder = "NIXPACKS"

[deploy]
startCommand = "uvicorn main:app --host 0.0.0.0 --port $PORT"
healthcheckPath = "/health"
restartPolicyType = "ON_FAILURE"
# requirements.txt — Python dependencies
fastapi
uvicorn[standard]
sqlalchemy
psycopg2-binary
python-dotenv
pydantic

Railway Pricing Note

Railway moved away from a free tier. The Hobby plan is ~$5/month (≈RM23) but gives you $5 in credits, so light usage may be effectively free. For FYPs where usage is low, you typically don't pay. Monitor your usage in the Railway dashboard.

🌍
12 — Custom Domain

Domain & Hostinger

A custom domain like myproject.com makes your FYP look professional. You buy the domain from a registrar (Hostinger is popular in Malaysia for affordable pricing), then point it at Vercel.

How Domains Work

When someone types myproject.com , their browser asks a DNS server "where is this?" DNS records map domain names to IP addresses or other servers. You control these records in Hostinger's dashboard.

DNS Record Type What It Does Example
A Record Points domain to an IP address @ → 76.76.21.21 (Vercel's IP)
CNAME Record Points domain to another domain name www → cname.vercel-dns.com
MX Record Points to email server For setting up email
TXT Record Stores verification text Used to prove you own the domain

Step-by-Step: Hostinger → Vercel

1
Buy a domain on Hostinger

hpanel.hostinger.com → Domains → search for your name. .com is ~RM30–50/year. .my is also an option.

2
Add domain to Vercel

Vercel Project → Settings → Domains → Add Domain → type your domain. Vercel shows you the DNS records to set.

3
Update DNS in Hostinger

Hostinger hPanel → Domains → DNS Zone → edit. Add the A record and CNAME record Vercel gives you.

4
Wait for propagation

DNS changes take 1–48 hours to propagate worldwide (usually under 30 minutes). Vercel automatically handles SSL certificates (HTTPS).

💡 Vercel automatically provisions a free SSL certificate (HTTPS) for your domain via Let's Encrypt. You don't need to buy or configure SSL separately.
🏗️
13 — The Big Picture

Full Stack Architecture

Option A — Next.js Only (Recommended for most FYPs)

If your backend logic is simple (CRUD operations, basic auth), keep everything in one Next.js app. Use API routes for server logic and Supabase for the database.

┌─────────────────────────────────────────────────────────┐
│                        USER                             │
│                        browser                          │
└──────────────────────┬──────────────────────────────────┘
                       │  HTTPS
┌──────────────────────▼──────────────────────────────────┐
│              VERCEL  (myproject.com)                     │
│                                                          │
│  Next.js App                                             │
│  ├── /app/page.tsx          → UI pages                   │
│  ├── /app/api/users/route.ts → Server API endpoints      │
│  └── /lib/supabase.ts       → DB client                 │
└──────────────────────┬──────────────────────────────────┘
                       │  DB Connection
┌──────────────────────▼──────────────────────────────────┐
│              SUPABASE                                    │
│              PostgreSQL database                         │
│              Auth, Storage, Realtime                     │
└─────────────────────────────────────────────────────────┘

Option B — Separate Frontend + Backend (For ML/IoT FYPs)

If your backend uses Python (for ML models, IoT, data processing), you need a separate backend server on Railway.

┌──────────────────────────────────────────────────────────┐
│                     USER / IoT Device                    │
└────────┬─────────────────────────────────────────────────┘
         │                              │
    Browser                      ESP32/Device
         │                              │
┌────────▼──────────┐     ┌─────────────▼────────────────┐
│   VERCEL          │     │   RAILWAY                     │
│   Next.js         │────▶│   FastAPI (Python)            │
│   Frontend        │     │   ML Model, Business Logic    │
└───────────────────┘     └─────────────┬────────────────┘┌─────────────▼────────────────┐
                          │   SUPABASE                   │
                          │   PostgreSQL                 │
                          └──────────────────────────────┘

Typical Data Flow

1
User visits your app

Browser loads your Next.js frontend from Vercel's CDN. The HTML renders in milliseconds.

2
Frontend fetches data

React components call your API (either Next.js API routes or Railway backend) to load dynamic content.

3
API queries the database

The server runs SQL queries against Supabase's PostgreSQL and returns JSON.

4
Frontend renders the data

React receives the JSON, updates component state, re-renders the UI.

Common Libraries You'll Need

Library Purpose Install
@supabase/supabase-js Connect to Supabase from Next.js npm i @supabase/supabase-js
@supabase/auth-helpers-nextjs Supabase auth with Next.js App Router npm i @supabase/ssr
prisma ORM for PostgreSQL npm i prisma @prisma/client
zod Schema validation for forms and API inputs npm i zod
recharts Chart library for React npm i recharts
react-hook-form Form state management npm i react-hook-form
date-fns Date formatting and manipulation npm i date-fns
axios HTTP client (alternative to fetch) npm i axios
lucide-react Beautiful icon library for React npm i lucide-react
shadcn/ui High-quality UI component library (Tailwind) npx shadcn@latest init
🔒
14 — Secrets Management

Environment Variables & Secrets

Never hardcode API keys, database passwords, or any sensitive value directly in your code. Use environment variables — they're loaded from a separate file that's never committed to Git.

How It Works

# .env.local  (in your Next.js project root)
# This file is in .gitignore — never pushed to GitHub

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://abc123.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...

# Railway backend URL
NEXT_PUBLIC_API_URL=https://myapp.railway.app

# Server-only secrets (no NEXT_PUBLIC_ prefix)
# These are NEVER sent to the browser
DATABASE_URL=postgresql://user:password@host:5432/db
SECRET_KEY=some-random-long-string
RESEND_API_KEY=re_abc123...

Accessing Variables in Code

// Client-side (NEXT_PUBLIC_ prefix = sent to browser)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;

// Server-side only (API routes, server components)
const dbUrl = process.env.DATABASE_URL;     // undefined in browser
const secret = process.env.SECRET_KEY;
🚨 Never use NEXT_PUBLIC_ prefix for sensitive secrets like DATABASE_URL or SECRET_KEY . The prefix means "expose this to the browser" — anyone visiting your site can read those values in the page source.

Where to Set Variables

Environment Where Notes
Local dev .env.local in project root In .gitignore, never committed
Vercel (prod) Project Settings → Environment Variables Set for Production / Preview / Dev separately
Railway Project → Variables tab Automatically injected at runtime
Final Step

FYP Submission Checklist

Code & Repository

  • GitHub repository is public (or shared with supervisor)
  • README.md explains what the project does and how to run it
  • Code is clean — no commented-out debug code, no console.log spam
  • .env.local is in .gitignore and NOT pushed to GitHub
  • A .env.example file exists showing which variables are needed (with dummy values)
  • All features are merged into main branch

Database

  • All tables have correct schema (right data types, NOT NULL where needed)
  • Row Level Security is enabled on Supabase tables (for production)
  • Database has sample/test data so the demo looks populated
  • Supabase project is not paused (send a query to keep it active)

Deployment

  • App is live and accessible via public URL
  • All environment variables are set in Vercel and Railway
  • HTTPS works (padlock icon in browser)
  • Custom domain is configured and resolving correctly
  • No broken pages or API errors on the live site

Performance & Quality

  • Run Lighthouse audit in Chrome DevTools — aim for green scores
  • App works on mobile screen sizes
  • Loading states are shown while data fetches
  • Error states are handled gracefully (no blank white screens)
🎓 Run npm run build locally before your final submission. If it builds without errors, Vercel will deploy successfully. Fix all TypeScript and ESLint errors before your presentation day.

FYP Web Dev Guide · Built for Next.js + Vercel + Supabase + Railway stack
Git → React → Next.js → Tailwind → Backend → Database → Deploy