Next.js এর হাতেখড়ি (পর্ব ১৪) – পূর্ণাঙ্গ প্রজেক্ট: Firebase + Server Actions + Cloudflare

Next.js পূর্ণাঙ্গ প্রজেক্ট Firebase Server Actions Cloudflare

Next.js এর হাতেখড়ি সিরিজের এটাই শেষ পর্ব। এতদিন Routing, Server/Client Components, Data Fetching, Rendering কৌশল, Loading/Error States, API Routes, Server Actions, Middleware, Firebase ইন্টিগ্রেশন, Image/SEO, আর Deployment — সবকিছু আলাদাভাবে শিখেছি। আজ সব একসাথে জুড়ে একটা সম্পূর্ণ বুক-শেয়ারিং অ্যাপ বানাব — এতে React, Firebase, আর Cloudflare — তিনটা সিরিজেরই জ্ঞান একসাথে কাজে লাগবে।

আর্কিটেকচার — কোন পর্ব কোথায় প্রযোজ্য

  • Firebase Auth (Firebase পর্ব ২-৩) — Email/Password লগইন
  • সেশন কুকি (Next.js পর্ব ১০) — Firebase Auth-কে সার্ভারের সাথে জোড়া লাগানো
  • Middleware (পর্ব ৯) — /dashboard প্রোটেক্ট করা
  • Firebase RTDB (Firebase পর্ব ৫-৮) — বইয়ের তথ্য, Security Rules সহ
  • Server Component + fetch (পর্ব ৪-৫) — বইয়ের তালিকা SSR-এ দেখানো
  • Server Actions (পর্ব ৮) — নতুন বই যোগ করার ফর্ম
  • next/image ও Metadata (পর্ব ১১) — কভার ছবি ও SEO
  • Cloudflare Workers (vinext) (পর্ব ১২) — লাইভ ডেপ্লয়মেন্ট

ফোল্ডার স্ট্রাকচার — সবকিছুর একটা মানচিত্র

app/
├── middleware.js              # /dashboard প্রোটেক্ট
├── layout.js                  # রুট লেআউট
├── login/
│   └── page.js                # Client Component — লগইন ফর্ম
├── api/
│   └── session/
│       └── route.js           # সেশন কুকি সেট করা
├── dashboard/
│   ├── page.js                 # Server Component — বইয়ের তালিকা
│   └── actions.js              # Server Actions — addBook, deleteBook
└── books/
    └── [id]/
        └── page.js             # ডাইনামিক রুট — একটা বইয়ের বিস্তারিত

middleware.js — সব একসাথে

// middleware.js
import { NextResponse } from "next/server";

export function middleware(request) {
  const sessionCookie = request.cookies.get("session");

  if (!sessionCookie) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

dashboard/actions.js — Server Actions

// app/dashboard/actions.js
"use server";

import { revalidatePath } from "next/cache";

export async function addBook(prevState, formData) {
  const title = formData.get("title");
  const author = formData.get("author");

  if (!title || title.trim() === "") {
    return { error: "বইয়ের নাম লিখতে হবে" };
  }

  await fetch(
    "https://my-first-app-default-rtdb.firebasedatabase.app/books.json",
    {
      method: "POST",
      body: JSON.stringify({ title, author, createdAt: Date.now() }),
    }
  );

  revalidatePath("/dashboard"); // ক্যাশ রিফ্রেশ (পর্ব ৮)
  return { success: true };
}

dashboard/page.js — Server Component + Server Action একসাথে

// app/dashboard/page.js
import Image from "next/image";
import { addBook } from "./actions";
import AddBookForm from "./AddBookForm"; // Client Component, নিচে

async function getBooks() {
  const res = await fetch(
    "https://my-first-app-default-rtdb.firebasedatabase.app/books.json",
    { cache: "no-store" } // SSR — সবসময় সাম্প্রতিক (পর্ব ৫)
  );
  return res.json();
}

export const metadata = {
  title: "আমার বইয়ের তালিকা | ArifStack",
};

export default async function DashboardPage() {
  const books = await getBooks();

  return (
    <div>
      <h1>আমার বইয়ের তালিকা</h1>

      <AddBookForm action={addBook} /> {/* Server Action পাস করা হচ্ছে */}

      <div style={{ display: "flex", flexWrap: "wrap", gap: "12px" }}>
        {Object.entries(books || {}).map(([id, book]) => (
          <div key={id}>
            <Image src={book.coverUrl || "/default-cover.jpg"} alt={book.title} width={100} height={150} />
            <p>{book.title} — {book.author}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

AddBookForm.js — useActionState দিয়ে Client Component

// app/dashboard/AddBookForm.js
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "সেভ হচ্ছে..." : "যোগ করো"}
    </button>
  );
}

export default function AddBookForm({ action }) {
  const [state, formAction] = useActionState(action, null);

  return (
    <form action={formAction}>
      <input name="title" placeholder="বইয়ের নাম" />
      <input name="author" placeholder="লেখক" />
      {state?.error && <p style={{ color: "red" }}>{state.error}</p>}
      <SubmitButton />
    </form>
  );
}

এই একটা ফিচারেই (নতুন বই যোগ করা) এই সিরিজের প্রায় সব বড় ধারণা একসাথে কাজ করছে — Server Component পেজ, ভেতরে একটা Client Component ফর্ম (পর্ব ৩), সেই ফর্ম একটা Server Action কল করছে (পর্ব ৮), যা RTDB-তে লিখছে (Firebase পর্ব ৬), তারপর ক্যাশ রিফ্রেশ করছে (পর্ব ৫), আর পুরো পেজ Middleware দিয়ে প্রোটেক্টেড (পর্ব ৯)।

ইন্টারেক্টিভ — পুরো অ্যাপের ডেটা ফ্লো

নিচে ইউজার লগইন থেকে বই যোগ করা পর্যন্ত সম্পূর্ণ প্রক্রিয়া দেখো।

ডেপ্লয় করা — পর্ব ১২-এর সিদ্ধান্ত প্রয়োগ

এই অ্যাপে Middleware, Server Actions, SSR — সবকিছুই আছে, তাই পর্ব ১২-এর চেকলিস্ট অনুযায়ী এটার জন্য vinext + Cloudflare Workers দরকার, শুধু static export যথেষ্ট না।

npx vinext init
npm run build
npx wrangler deploy

# সিক্রেট যোগ করা (পর্ব ১০, ১৩)
# wrangler secret put FIREBASE_DB_SECRET

নিজে চেষ্টা করো

Exercise:

  1. উপরের ফোল্ডার স্ট্রাকচার নিজের প্রজেক্টে বানাও।
  2. Firebase Auth লগইন + সেশন কুকি + Middleware প্রোটেকশন একসাথে কাজ করাও (পর্ব ৯-১০)।
  3. addBook Server Action বানাও, RTDB-তে লেখো, revalidatePath ব্যবহার করো।
  4. useActionStateuseFormStatus দিয়ে ফর্মে ভ্যালিডেশন ও লোডিং স্টেট যোগ করো।
  5. পর্ব ১৩-এর প্রোডাকশন চেকলিস্ট দিয়ে যাচাই করো, তারপর vinext দিয়ে Cloudflare-এ ডেপ্লয় করো।

সিরিজ শেষ — তিন সিরিজ মিলিয়ে যা শিখলাম

React এর হাতেখড়ি (UI ও কম্পোনেন্ট), Firebase এর হাতেখড়ি (Auth ও Database), Cloudflare এর হাতেখড়ি (হোস্টিং ও ইনফ্রা), আর এখন Next.js এর হাতেখড়ি (এই তিনটাকে একটা সুসংগত ফ্রেমওয়ার্কে জোড়া লাগানো) — এই চারটা সিরিজ মিলিয়ে এখন একটা সম্পূর্ণ, আধুনিক, প্রোডাকশন-রেডি ওয়েব অ্যাপ্লিকেশন বানানোর সবগুলো টুকরো হাতে আছে। সামনের সেরা পদক্ষেপ হলো এই পুরো স্ট্যাক ব্যবহার করে নিজের একটা বাস্তব প্রজেক্ট আইডিয়া দাঁড় করানো।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম