איך להשתמש ב Next.JS בתור Client Side Router

28/02/2025

בונים אפליקציית Client Side בלבד ב React? תשמחו לשמוע שאתם יכולים לעשות את זה עם next.js גם בלי להשתמש בשרת שלהם, ולבנות את האפליקציה בדיוק כמו שבונים אפליקציית vite רגילה. יותר מזה, עם next אתם כבר מקבלים Router צד לקוח ויכולת לשדרג בעתיד לאפליקציית נקסט הכוללת גם שרת. בואו נראה איך זה עובד.

1. אפליקציית צד לקוח עם מעבר דפים

אני יוצר אפליקציית Next.js לפי תבנית שהכנתי מראש עם הפקודה:

npx create-next-app@latest app -e https://github.com/ynonp/next-15-starter

הטמפלייט חוסך לכם את כל השאלות של נקסט ומנקה את רוב הקוד. אני ממשיך ויוצר תיקיה בשם about ובתוכה קובץ בשם page.tsx עם התוכן הבא:

import Nav from "../components/nav"
export default function About() {
  return (
    <div>
      <Nav active="/about" />
      <h1 className="text-3xl font-bold underline">
        About us
      </h1>
    </div>
  )
}

וככה יצרתי עמוד about בפרויקט. אני ממשיך ומעדכן את הדף הראשון page.tsx בתיקייה הראשית לקוד הזה:

import Nav from "./components/nav"
export default function Home() {
  return (
    <div>
      <Nav active="/" />
      <h1 className="text-3xl font-bold underline">
        Hello world!
      </h1>

    </div>
  )
}

ונשאר רק לכתוב את components/nav.tsx שהוא קומפוננטת הניווט:

import Link from "next/link"

export default function Nav({active}: {
  active: string,
}) {
  return (
    <nav>
      <p>active = {active}</p>
      <ul>
        <li className={active == "/" ? "active" : ""}>
          <Link href="/">Home</Link>            
        </li>
        <li className={active == "/about" ? "active" : ""}>
          <Link href="/about">About Us</Link>
        </li>
      </ul>
  </nav>
  )
}

אפשר כבר להריץ עם npm run dev ולראות את שני הדפים ותפריט הניווט שעובר ביניהם, אבל אנחנו כאן בשביל אפליקציית צד לקוח בלי שרת לכן נכנס לקובץ next.config.js ונעדכן אותו ל:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: 'export',
  trailingSlash: true,
};

export default nextConfig;

עכשיו נריץ:

npm run build

וקיבלנו בתיקיית out פרויקט צד-לקוח שמכיל שני דפים עם ראוטר צד-לקוח שעובר ביניהם. אפשר להפעיל את הפרויקט כאפליקציית צד לקוח עם:

cd out
npx local-server

2. איך מוסיפים קוד טיפול באירועים?

נחזור ל page.tsx הראשי וננסה לבנות שם מונה לחיצות:

import Nav from "../components/nav"
import { useState } from "react"

export default function About() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Nav active="/about" />
      <h1 className="text-3xl font-bold underline">
        About us
      </h1>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>

  )
}

הפעם ניסיון לבנות את האפליקציה נכשל עם השגיאה:

./src/app/about/page.tsx
Error:   x You're importing a component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive.
  |
  |  Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
  |
  |
   ,-[/Users/ynonp/tmp/nextgames/app/src/app/about/page.tsx:2:1]
 1 | import Nav from "../components/nav"
 2 | import { useState } from "react"
   :          ^^^^^^^^
 3 |
 4 | export default function About() {
 5 |   const [count, setCount] = useState(0);
   `----

Import trace for requested module:
./src/app/about/page.tsx

זה קרה בגלל שהקומפוננטה בברירת מחדל לא כוללת את קוד ה JavaScript של ריאקט - כלומר היא Server Component. בשביל להוסיף קוד טיפול באירועים עלינו להגדיר אותה כ Client Component על ידי הוספת המחרוזת use client בתחילת הקובץ באופן הבא:

'use client'
import Nav from "../components/nav"
import { useState } from "react"

export default function About() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Nav active="/about" />
      <h1 className="text-3xl font-bold underline">
        About us
      </h1>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>

  )
}

כתיבת Server Components מייצרת פחות קוד JavaScript ולכן נעדיף להשתמש בהם כשאנחנו יכולים, אבל כמובן שכשצריך לוגיקה בצד לקוח כמו מונה לחיצות נצטרך לעבור ל Client Components.

3. ומה לגבי שליפת מידע בצד לקוח?

ל Next יש מנגנון מובנה של העברת מידע מ Server Components לדפדפן ומנגנון מובנה אחר שעוטף את קריאות ה Ajax באפליקציית Full Stack. כל זה לא רלוונטי כשכותבים אפליקציית צד-לקוח בלבד.

באפליקציית צד-לקוח נוכל להשתמש ב react-query כמו שאנחנו רגילים כדי למשוך מידע מ APIs חיצוניים.

4. מה עם הפנייה תכנותית בין עמודים?

ראינו את Link שמאפשר למשתמש לעבור בין דפים דרך לחיצה, אבל ל next יש גם מנגנון תכנותי של מעבר נתיבים לדוגמה הקוד הבא יחזור אוטומטית לדף הבית כשמונה הלחיצות מגיע ל 10:

'use client'
import Nav from "../components/nav"
import { useState } from "react"
import { useRouter } from "next/navigation";

export default function About() {  
  const [count, setCount] = useState(0);
  const router = useRouter();

  function handleClick() {
    if (count === 9) {
      // next click is 10, so we redirect
      return router.push('/');
    }
    setCount(c => c + 1);
  }

  return (
    <div>
      <Nav active="/about" />
      <h1 className="text-3xl font-bold underline">
        About us
      </h1>
      <button onClick={handleClick}>{count}</button>
    </div>
  )
}

לסיכום ה Router של next נותן פיתרון מצוין לניווט בין דפים גם באפליקציית Client Side בלבד ואפשר להשתמש ב next כסביבה לפיתוח ריאקט כמו שמשתמשים ב vite. ומה עם ריאקט ראוטר? אישית אני לא מתגעגע ובכל מקרה נשים לב שהפיתרון של נקסט פשוט יותר וגם יאפשר לנו בעתיד לשדרג ל SSR אם וכאשר נצטרך.