Menu

22. Real-World Projects

Next.js Master Roadmap - IT Technology

This chapter walks through a series of real‑world Next.js projects ranging from beginner‑level static sites and blogs to intermediate e‑commerce and LMS systems, and advanced SaaS, AI chat, marketplace, and social platforms, illustrating key concepts, architectures, and implementation details.

No MCQ questions available for this chapter.

22. Real-World Projects

Beginner Projects

Static Portfolio Site

The first project is a static showcase of personal work built with Next.js, Tailwind CSS, and Markdown for blog‑style entries. The site is deployed on Vercel, taking advantage of its global edge network for instant loading.

  • Styling: Tailwind’s utility‑first classes enable rapid UI construction without leaving the HTML.
  • Dark mode: Implemented via next-themes; a simple toggle switches the data-theme attribute on the <html> element.
  • Contact form: Uses Formspree; the form posts to a Formspree endpoint, which forwards submissions to an email address.
  • SEO: Metadata is injected with next/head, allowing each page to define its own <title>, <meta name="description">, and Open Graph tags.

Example of a page’s head section:

import Head from 'next/head';

export default function Home() {
  return (
    <>
      <Head>
        <title>My Portfolio – Projects & Blog</title>
        <meta name="description" content="Showcase of my Next.js projects, blog posts, and open‑source contributions." />
        <meta property="og:title" content="My Portfolio" />
        <meta property="og:image" content="/og-image.jpg" />
      </Head>
      {/* page content */}
    </>
  );
}

MDX‑Enabled Blog Platform

Building on the portfolio, the blog adds MDX support, author bios, pagination, and an RSS feed. Performance optimizations include automatic font loading with next/font, Incremental Static Regeneration (ISR), and image optimization via next/image.

  • MDX: Allows mixing JSX components inside Markdown, enabling interactive demos within posts.
  • Author bios: Stored as JSON files; each post imports the relevant author object.
  • Pagination: Implemented with getStaticProps returning a limited set of posts plus nextPage/prevPage URLs.
  • RSS feed: Generated at build time using the rss package; the feed URL is /rss.xml.
  • Font optimization: next/font automatically self‑hosts Google Fonts, eliminating external requests.
  • ISR: export const revalidate = 60; tells Next.js to regenerate the page at most once every 60 seconds.
  • Image optimization: next/image serves responsively sized images with lazy loading and placeholder blur.

Sample ISR page:

export const revalidate = 60; // revalidate every 60 seconds

export async function getStaticProps() {
  const posts = await fetchPosts(); // fetch from CMS or filesystem
  return { props: { posts } };
}

export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map(p => (
        <li key={p.id}>
          <Link href={`/blog/${p.slug}`}>{p.title}</Link>
        </li>
      ))}
    </ul>
  );
}

CRUD Notes Application

The notes app demonstrates full‑cycle state management with React Hook Form, schema validation via Zod, and a data‑migration strategy that moves notes from localStorage to a PlanetScale‑hosted SQLite‑compatible database using Prisma.

  • State management: React Hook Form handles form values, errors, and resets with minimal boilerplate.
  • Zod schema: Enforces a minimum title length and ensures content exists.
  • Persistence layer: Initial version writes to localStorage for instant feedback; a background sync writes to Prisma, which then pushes to PlanetScale.
  • Prisma schema: Defines a Note model with title and content fields.

Zod validation example:

import { z } from 'zod';

const noteSchema = z.object({
  title: z.string().min(3, 'Title must be at least 3 characters'),
  content: z.string().nonempty('Content is required'),
});

export default function NoteForm() {
  const { register, handleSubmit, reset } = useForm({ resolver: zodResolver(noteSchema) });
  const onSubmit = data => {
    // data is guaranteed to match the schema
    createNote(data);
    reset();
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('title')} placeholder="Title" />
      <textarea {...register('content')} placeholder="Note" />
      <button type="submit">Save</button>
    </form>
  );
}

Prisma model:

model Note {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  createdAt DateTime @default(now())
}

Intermediate Projects

E‑commerce Store

This project implements a full product catalog, shopping cart, Stripe checkout, and an admin dashboard for order management. Server Actions handle mutations, Redis caches cart data, and next/image optimizes product thumbnails.

  • Product catalog: Fetched from a PostgreSQL database via Prisma; each product includes images, price, and inventory.
  • Cart: Stored in Redis as a hash cart:{userId} where fields are product IDs and values are quantities.
  • Stripe integration: Uses Stripe Checkout for one‑time payments; webhook endpoint verifies checkout.session.completed events.
  • Admin dashboard: Protected route that lists orders, allows status updates, and exports CSV reports.
  • Server Actions: Enable async functions to be called directly from form elements, reducing API boilerplate.

Server Action for adding to cart:

'use server';

import { redis } from '@/lib/redis';

export async function addToCart(formData: FormData) {
  const userId = formData.get('userId') as string;
  const productId = formData.get('productId') as string;
  const qty = parseInt(formData.get('qty') as string, 10) || 1;

  await redis.hset(`cart:${userId}`, productId, qty.toString());
  return { success: true };
}

Calling the action from a form:

<form action={addToCart}>
  <input type="hidden" name="userId" value="{userId}" />
  <input type="hidden" name="productId" value="{productId}" />
  <input type="number" name="qty" defaultValue="1" />
  <button type="submit">Add to cart</button>

Learning Management System (LMS)

The LMS supports course creation, video streaming through Mux, interactive quizzes powered by React‑Query, role‑based access control via NextAuth.js (JWT strategy), and automatic certificate generation with pdfkit.

  • Course creation: Instructors upload video files; Mux creates streaming URLs and generates thumbnails.
  • Video streaming: Uses Mux Player (@mux/mux-player) for adaptive HLS/DASH playback.
  • Quizzes: Data fetched with React‑Query; optimistic updates improve perceived performance.
  • Authentication: NextAuth.js with JWT stores a signed token in an HTTP‑only cookie; middleware checks role claims.
  • Certificate generation: After course completion, a PDF is generated server‑side using pdfkit and streamed to the user.

Mux video upload example:

import { Video } from '@mux/mux-node';

const { Video } = new Mux(tokenId, tokenSecret);

export async function POST(req) {
  const form = await req.formData();
  const file = form.get('video') as File;
  const buffer = Buffer.from(await file.arrayBuffer());

  const upload = await Video.Uploads.create({
    asset_ids: [], // will be filled after upload completes
    cors_origin: '*',
    new_asset_settings: {
      playback_policy: ['public'],
    },
  });

  // PUT request to upload URL (omitted for brevity)
  return new Response(JSON.stringify({ uploadId: upload.id }), { status: 200 });
}

PDF certificate generation with pdfkit:

import { PDFDocument } from 'pdfkit';
import { Readable } from 'stream';

export async function generateCertificate({ name, course }) {
  const doc = new PDFDocument({ size: 'A4', margin: 50 });
  const chunks: Uint8Array[] = [];
  doc.on('data', chunk => chunks.push(chunk));
  doc.on('end', () => {
    const pdfBuffer = Buffer.concat(chunks);
    // stream pdfBuffer as response
  });

  doc.fontSize(26).text('Certificate of Completion', { align: 'center' });
  doc.moveDown();
  doc.fontSize(16).text(`This certifies that ${name}`, { align: 'center' });
  doc.fontSize(16).text(`has successfully completed the course "${course}".`, { align: 'center' });
  doc.moveDown();
  doc.fontSize(12).text(`Date: ${new Date().toLocaleDateString()}`, { align: 'right' });
  doc.end();
  return Readable.from(chunks);
}

Job Portal

Job listings feature search/filter via useSearchParams, resume uploads through UploadThing, application tracking, and email notifications powered by SendGrid.

  • Search/filter: The URL’s query string drives the UI; updating useSearchParams updates the list without a full reload.
  • Resume upload: UploadThing provides a secure, typed upload endpoint; files are stored in an S3‑compatible bucket.
  • Application tracking: Each applicant’s status (applied, reviewed, interviewed, hired) is stored in a PostgreSQL table.
  • Email notifications: SendGrid’s @sendgrid/mail sends templated emails on status changes.

Using useSearchParams for filtering:

import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';

export default function JobList() {
  const searchParams = useSearchParams();
  const [filters, setFilters] = useState({
    q: searchParams.get('q') ?? '',
    location: searchParams.get('location') ?? '',
    remote: searchParams.get('remote')