en
Feedback
ToCode

ToCode

Open in Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Show more
1 420
Subscribers
+124 hours
+17 days
-430 days
Posts Archive
ToCode
1 420
הורדתי את הטקסט המלא כי הוא לא נכנס טוב לטלגרם עדיף להיכנס ללינק לקרוא מהאתר

ToCode
1 420
מדריך next.js חלק 8 - העלאה לשרת נסיים את המדריך בחיבור עם ורסל לצורך העלאת האתר שכתבנו לאינטרנט. תיקון שגיאות בניה לפני שאפשר להעלות את הקוד לשרת צריך לוודא שהוא נבנה בלי שגיאות, ואם עקבתם אחרי המדריך עד כאן תצטערו לשמוע שזה לא המקרה. במהלך העבודה עשיתי מספר קיצורי דרך שאומנם היו קלים יותר לכתיבה והסבר אבל הרגיזו את next. אלה הבעיות המרכזיות: 1. בכל הקומפוננטות ייצאתי פונקציה אנונומית. נקסט אוהב שיש שם לכל קומפוננטה ולכן אני מחליף כל יצירת קומפוננטה לפונקציה ממש עם שם, כלומר במקום לכתוב:
export default () => { ... }
אני כותב:
export default function AboutPage() { ... }
2. בקומפוננטת התפריט היה לי מערך של פריטים שכללו נתיב על השרת. ההגדרה המקורית של המערך היתה:
items: Array<{href: string, text: string}>
אבל נקסט רוצה לעשות בדיקת טיפוסים גם על הנתיבים שאני מעביר לאלמנט Link ולכן ה href לא יכול להיות כל string אלא צריך להיות מחרוזת שהיא גם נתיב תקני על השרת. בשביל לתקן את שגיאת הבניה החלפתי את ההגדרה ל:
export default function Menu<T extends string>({ items }: {
  items: Array<{href: Route<T>, text: string}>
}) {
3. הוספתי מאפיין key לכל רשימה. זו רשימת השגיאות המלאה שתיקנתי:
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index cf82120..f697b7c 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -1,8 +1,8 @@
-export default () => {
+export default function About() {
   return (
     <div>
       <h1>About Us</h1>
-      <p>This is an example lesson for using next.js router</p>
+      <p>This is an example lesson for using next.js</p>
     </div>
   )
 }
\ No newline at end of file
diff --git a/src/app/components/menu.tsx b/src/app/components/menu.tsx
index 936ca44..468dfcc 100644
--- a/src/app/components/menu.tsx
+++ b/src/app/components/menu.tsx
@@ -1,16 +1,17 @@
 "use client";
 import Link from 'next/link';
+import type { Route } from 'next'
 import { usePathname } from 'next/navigation'
 
 
-export default ({ items }: {
-  items: Array<{href: String, text: String}>
-}) => {
+export default function Menu<T extends string>({ items }: {
+  items: Array<{href: Route<T>, text: String}>
+}) {
   const pathname = usePathname()
   return (
     <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
       {items.map(item => (
-      <div className='flex-1 px-2'>
+      <div className='flex-1 px-2' key={item.href}>
       <Link href={item.href} >{item.text}</Link>
       {pathname == item.href &&
       <span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
diff --git a/src/app/counter.tsx b/src/app/counter.tsx
index 50da28f..c4bb5a4 100644
--- a/src/app/counter.tsx
+++ b/src/app/counter.tsx
@@ -2,7 +2,7 @@
 import { useState } from 'react';
 import Header from './header';
 
-export default () => {
+export default function Counter() {
   const [count, setCount] = useState(0);
   console.log('counter');
   return (
diff --git a/src/app/header.tsx b/src/app/header.tsx
index 0ff3c50..a9e22f5 100644
--- a/src/app/header.tsx
+++ b/src/app/header.tsx
@@ -1,4 +1,4 @@
-export default () => {
+export default function Header() {
   console.log('header');
   return <h1>Counter Header</h1>
 }
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 5cd02da..b27557b 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,7 +2,7 @@ import type { Metadata } from 'next'
 import { UserProvider } from '@auth0/nextjs-auth0/client';
 
 import { Inter } from 'next/font/google'
-import TopMenu from './servermenu';
+import TopMenu from './menu';
 import './globals.css'
 
 const inter = Inter({ subsets: ['latin'] })
diff --git a/src/app/menu.tsx b/src/app/menu.tsx
index 1e40b9c..2043559 100644
--- a/src/app/menu.tsx
+++ b/src/app/menu.tsx
@@ -2,7 +2,7 @@ import Menu from './components/menu';
 import { readdir } from 'node:fs/promises';
 import { existsSync } from 'node:fs';
 
-export default async () => {

ToCode
1 420
<span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
      }
    </div>
      ))}
    </nav>
  )
}
עכשיו קומפוננטת צד הלקוח עושה שני דברים: היא גם מקבלת בצורה דינמית את רשימת הדפים מהשרת, וגם משתמשת ב usePathname כדי להציג את העיגול ליד הפריט הפעיל בתפריט. ובאופן כללי נרצה לבנות יישומי Next.js במבנה של שיתוף פעולה בין קומפוננטות צד שרת לקומפוננטות צד לקוח. קומפוננטת צד שרת תמשוך את המידע, למשל דרך פנייה לבסיס נתונים, ל API מרוחק או למערכת קבצים, תעביר את המידע לקומפוננטת צד לקוח דרך props וקומפוננטת צד לקוח תוכל להציג את המידע בצורה אינטרקטיבית תוך שימוש בסטייט. כשאותה קומפוננטת צד לקוח תצטרך לעדכן חזרה את השרת נשתמש במנגנון שראינו בפרק הקודם של המדריך וכך העדכון יגרום לריענון של קומפוננטות צד השרת שבעמוד ובמידה והמידע ישתנה גם קומפוננטות צד הלקוח יקבלו פרופס חדשים ויציגו את התוכן המעודכן על המסך.

ToCode
1 420
מדריך Next.JS חלק 6 - שילוב קומפוננטות צד-לקוח וצד-שרת בואו נחזור לתפריט העליון שכתבנו באפליקציה. אם עקבתם אחרי המדריך והתרגילים צריכות להיות לכם שתי גירסאות לקומפוננטה זו - גירסת קומפוננטת צד-שרת של התפריט העליון, שמחפשת בצורה דינמית את כל התיקיות שמייצגות "דפים" ומציגה תפריט שמיוצר אוטומטית מהדפים הקיימים, וגירסת קומפוננטת צד-לקוח שמשתמשת ברשימה קבועה של דפים אבל כוללת עיגול קטן ליד הדף הפעיל. בחלק זה נראה איך לשלב את שתי הקומפוננטות כדי ליהנות מהטוב משני העולמות - גם לקבל תפריט שנוצר דינמית מתוך התיקיות בשרת, וגם לקבל סימון לנתיב הפעיל בעזרת JavaScript בצד לקוח. מה בעצם הבעיה בואו ניזכר בשתי קומפוננטות התפריט ובבעיות של כל אחת מהן. התפריט בקומפוננטת צד לקוח נראה כך:
"use client";
import Link from 'next/link';
import { usePathname } from 'next/navigation'

export default () => {
  const pathname = usePathname()
  return (
    <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
      <div className='flex-1 px-2'>
        <Link href="/" >Home</Link>
        {pathname == "/" &&
        <span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
        }

      </div>
      <div className='flex-1 px-2'>
        <Link href="/about" >About</Link>
        {pathname == "/about" &&
        <span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
        }
      </div>
    </nav>
  )
}
בגלל שזו קומפוננטת צד לקוח אני לא יכול להשתמש בפונקציות של מערכת הקבצים של השרת כמו readdir כדי לקבל את רשימת כל התיקיות על השרת, ולכן אני משתמש ברשימה קבועה. בצד השרת שלחתי אתכם באחד התרגילים לכתוב קומפוננטת צד-שרת שמוצאת את כל הדפים שצורה דינמית ויוצרת מהם תפריט, ואולי כתבתם קומפוננטה שנראית כך:
import { readdir } from 'node:fs/promises';
import Link from 'next/link';
import { existsSync } from 'node:fs';

export default async () => {  
  const pages = [
    {href: '/', text: 'home'},
    ...(await readdir('./src/app', { withFileTypes: true }))
    .filter(f => f.isDirectory)
    .filter(f => existsSync(\${f.path}/${f.name}/page.tsx\))
    .map(f => (
      {href: '/' + f.name, text: f.name}
    ))
  ]
    
  return (
    <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
      {pages.map(page => (
        <div className='flex-1 px-2'>
          <Link href={page.href} >{page.text}</Link>
        </div>))}
    </nav>
  )
}
בקומפוננטה זו אני לא יכול להשתמש ב usePathname() מאחר והגישה ל pathName מוגבלת לקומפוננטות צד-לקוח בלבד. הפיתרון - שילוב כוחות המבנה של קומפוננטת צד שרת שיודעת לגשת למידע וקומפוננטת צד לקוח שיודעת להציג את המידע בצורה אינטרקטיבית הוא בעצם תבנית די נפוצה באפליקציות ווב ובאפליקציות next.js במיוחד. הפיתרון לאתגר הזה הוא פשוט לחלק את העבודה לשתי קומפוננטות, כך שקומפוננטת צד שרת מרנדרת קומפוננטת צד לקוח ומעבירה לה את המידע שהיא צריכה בתור props. בקובץ אחד בשם menu.tsx אני כותב את התוכן הבא לקומפוננטת צד השרת:
import Menu from './components/menu';
import { readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';

export default async () => {  
  const pages = (await readdir('./src/app', { withFileTypes: true }))
  .filter(f => f.isDirectory)
  .filter(f => existsSync(\${f.path}/${f.name}/page.tsx\))
  .map(f => (
    {href: '/' + f.name, text: f.name}
  ))

  return <Menu items={[
    {href: "/", text: "home"},
    ...pages
  ]} />
}
ובקובץ אחר בשם components/menu.tsx אני כותב את התוכן הבא:
"use client";
import Link from 'next/link';
import { usePathname } from 'next/navigation'


export default ({ items }: {
  items: Array<{href: String, text: String}>
}) => {
  const pathname = usePathname()
  return (
    <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
      {items.map(item => (
      <div className='flex-1 px-2'>
      <Link href={item.href} >{item.text}</Link>
      {pathname == item.href &&

ToCode
1 420
משחק טוב עם הקוד בנקודה הזאת יהיה להוסיף עוד פעולות על הפוסטים - נסו להוסיף אפשרויות לעריכה ומחיקה של פוסטים. אחרי שזה עובד הוסיפו קומפוננטה חדשה שמציגה את מספר הפוסטים בעמוד ושימו לב שהמידע שבה מתרענן כל פעם שאתם מוסיפים או מוחקים פוסט.

ToCode
1 420
מדריך Next.JS חלק 5 - עדכון בסיס הנתונים מטופס בדפדפן בחלק הקודם של המדריך בנינו רשימת פוסטים שנוצרה בצורה סטטית על השרת בעזרת סקריפט seed.ts. הכח האמיתי של next.js הוא האינטגרציה בין קוד צד שרת לקוד צד לקוח, שעובדת כמעט בלי שנשים לב שיש פה רכיבי תוכנה שרצים על מכונות שונות. בואו נראה איך זה עובד דרך הוספת טופס ליישום שיוסיף פוסט חדש לעמוד הפוסטים. פונקציה ליצירת פוסט חדש ניצור תיקייה בשם db ובתוכה קובץ בשם posts.ts עם התוכן הבא:
"use server"

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

export async function createPost(author: string, text: string) {
  const res = await prisma.post.create({
    data: {
      author,
      text,
    }
  })
  
  return res;
}
הקוד דומה מאוד למה שכתבנו בקובץ ה seed.ts, רק שהפעם נמצא בתוך פונקציה שאפשר להשתמש בה גם מקבצים אחרים. הפתיח use server לקובץ גורם ל next.js לא להכניס את הקוד הזה ל JavaScript שירוץ בצד הלקוח. קומפוננטת צד לקוח עבור הטופס בפריימוורקים "רגילים" לכתיבת קוד צד שרת היינו צריכים עכשיו לכתוב נתיב ב API שיקרא לפונקציה, ואז לכתוב קוד בצד של ריאקט שיפנה לנתיב הזה כשלוחצים על איזשהו כפתור באפליקציה. ב next.js חסכו לנו את כל העבודה ואנחנו יכולים פשוט "לקרוא" לפונקציה createPost ישירות מקומפוננטת ריאקט. אני יוצר קובץ חדש בתיקיית posts בשם newpost.tsx והקוד שלו נראה ככה:
"use client";
import { useRef } from 'react';
import { useRouter } from 'next/navigation';
import { createPost
 } from "../db/posts"

export default () => {
  const router = useRouter();
  const textFieldRef = useRef<HTMLInputElement>(null);

  async function handleCreate(formData: FormData) {
    const author = formData.get('author') as string;
    const text = formData.get('text') as string;
    const newPost = await createPost(author, text);
    console.log(newPost);
    if (textFieldRef.current) {
      textFieldRef.current.value = '';
    }
    router.refresh();
  }

  return (
    <form action={handleCreate}>
      <label>
        Author: 
        <input type="text" name="author" className="text-black"/>
      </label>
      
      <label>
        Text: 
        <input type="text" name="text" className="text-black" ref={textFieldRef} />
      </label>
      <input type="submit" value="Create" />
    </form>
  )
}
הבלוק הראשון לקרוא כאן הוא הגדרת הפונקציה handleCreate. בואו נסתכל עליה:
async function handleCreate(formData: FormData) {
  const author = formData.get('author') as string;
  const text = formData.get('text') as string;
  const newPost = await createPost(author, text);
  console.log(newPost);
  if (textFieldRef.current) {
    textFieldRef.current.value = '';
  }
  router.refresh();
}
הפונקציה מקבלת אוביקט מסוג FormData, מושכת ממנו שני שדות עבור כותב הפוסט והטקסט של הפוסט ואז מגיעה לשורת המחץ שזו הקריאה ל createPost. זו בעצם הקריאה דרך API לפונקציה שנמצאת בצד השרת. החלק השני של הפונקציה, זה שמופיע אחרי ה await ל createPost ייקרא אחרי שהשרת יסיים ליצור את הפוסט החדש. בשלב הזה יש לנו שתי משימות: אנחנו צריכים לנקות חלק מהשדות בטופס (אני רציתי לנקות את שדה ה text כדי שיהיה קל להמשיך לכתוב פוסטים), ולרענן את רשימת הפוסטים כדי לראות את הפוסט החדש. הפונקציה router.refresh שמגיעה מתוך ה router של next היא שאחראית לקסם השני. הפונקציה מרעננת את קומפונטטות צד השרת שנמצאות על הדף הנוכחי, בלי למחוק את הסטייט של קומפוננטות צד הלקוח. כך רשימת הפוסטים מתעדכנת אבל שאר העמוד לא מושפע. כל מה שצריך בשביל לסיים את הדוגמה הוא להוסיף לקובץ posts/page.tsx את קומפוננטת הטופס החדש שבנינו ונוכל להיכנס לעמוד ולהוסיף פוסטים:
import { PrismaClient } from '@prisma/client'
import NewPost from './newpost';
const prisma = new PrismaClient()

export default async () => {
  const posts = await prisma.post.findMany();

  return (
    <main className='p-2'>
      <NewPost />
      <ul>
        {posts.map(post => (
          <li><b>{post.author}</b> {post.text}</li>
        ))}
      </ul>
    </main>
  )
}
עכשיו אתם

ToCode
1 420
2. בואו נשחק עם מנגנון ה Cache של נקסט - הוסיפו עמוד נוסף שמציג את התאריך ושעה הנוכחיים עם new Date. נסו לנווט אליו דרך התפריט ושימו לב מתי הערך ישתנה. נסו גם לרענן את העמוד עם F5 ושימו לב לשינויים. 3. הוציאו את ה Link יחד עם העיגול שלו לקומפוננטה נפרדת כדי שתוכלו להפוך את קומפוננטת התפריט חזרה לקומפוננטת צד-שרת.

ToCode
1 420
מדריך Next.JS חלק 3 - ניווט בין דפים אחד האתגרים המשמעותיים בפיתוח Single Page Application ושימוש ב JavaScript Frameworks היה תמיד הניווט בין דפים. בפעולה הרגילה שלו בדפדפן כל פעם שעוברים לדף חדש באתר הדפדפן טוען קובץ HTML חדש ואת כל הנכסים שלו ומציג את התוכן החדש, וכמובן מנקה את כל הזיכרון של ה JavaScript. הבעיה ביישומי Single Page Applications, או אולי יותר נכון להגיד היתרון ביישומים כאלה, הוא שעכשיו אין צורך לטעון את כל הדף מחדש במעבר בין דפים, ושבעזרת JavaScript אפשר לתת למשתמש הרגשה של מעבר בין דפים בלי לעבור דרך מנגנון זה בדפדפן, וכך לקבל ביצועים הרבה יותר טובים. איך זה עובד ב Next.JS מנגנון המעבר בין דפים מובנה בתוך הפריימוורק. ל next יש קומפוננטה מיוחדת בשם Link שלחיצה עליה גורמת ל JavaScript לטעון את הקוד של ה"דף" הבא ולהציג אותו. כמובן שהם מטפלים בשבילנו בלחיצות על כפתור אחורה בדפדפן ובשינוי כתובת העמוד (בשביל סימניות ושיתוף). בנוסף Next ישמור בזיכרון מטמון בדפדפן את התוצאות שקיבל מהשרת כך שניווט לדפים אחורה יהיה מהיר יותר. את הדפים עצמם אנחנו מגדירים בתור תיקיות חדשות בתיקיית הפרויקט כאשר בכל תיקיה שמייצגת דף צריך להיות קובץ בשם page.tsx שאחראי על התוכן של הדף. בואו ננסה את זה עם האפליקציה שהתחלנו לכתוב בחלקים הקודמים של המדריך. ניצור תיקיה חדשה בשם about ובתוכה קובץ בשם page.tsx עם התוכן הבא:
export default () => {
  return (
    <div>
      <h1>About Us</h1>
      <p>This is an example lesson for using next.js router</p>
    </div>
  )
}
רק עד כאן זה מספיק כדי לראות את שני הדפים שלנו. נווטו לשתי הכתובות:
http://localhost:3000
http://localhost:3000/about
כדי לראות את שני הדפים שיצרנו. מעבר בין דפים נוסיף שורת תפריט עליון כדי לעבור בין הדפים. צרו קובץ חדש בתיקיה הראשית בשם menu.tsx עם התוכן הבא:
import Link from 'next/link';

export default () => {
  return (
    <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
      <Link href="/" className='flex-1 px-2'>Home</Link>
      <Link href="/about" className='flex-1 px-2'>About</Link>
    </nav>
  )
}
נוסיף את התפריט העליון לקובץ layout.tsx באמצעות השינוי הבא:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import TopMenu from './menu';
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <TopMenu />
        {children}
      </body>
    </html>
  )
}

עכשיו כשנכנס מחדש לדף אנחנו רואים תפריט עליון עם שני הלינקים, ובנוסף לחיצה על כל אחד מהלינקים תגרום לשינוי הדף הפעיל ולהצגת הדף עליו לחצנו. הצגת הדף הנוכחי בשביל להבין מי הדף הנוכחי ולשנות את העיצוב שלו בתפריט אנחנו יכולים להשתמש ב Hooks בשם usePathname. העדכון הבא לקומפוננטת התפריט יגרום להצגת עיגול קטן ליד הדף הנוכחי כדי שנדע איפה אנחנו:
"use client";
import Link from 'next/link';
import { usePathname } from 'next/navigation'

export default () => {
  const pathname = usePathname()
  return (
    <nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
      <div className='flex-1 px-2'>
        <Link href="/" >Home</Link>
        {pathname == "/" &&
        <span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
        }
        
      </div>
      <div className='flex-1 px-2'>
        <Link href="/about" >About</Link>
        {pathname == "/about" &&
        <span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
        }
      </div>
    </nav>
  )
}
עכשיו אתם 1. הוסיפו דף נוסף לאפליקציה בשם Contact והציגו בו טופס "צור קשר". שימו לב לעדכן את התפריט כדי להציג את שלושת הדפים.

ToCode
1 420
הנה עוד כמה משחקים שכדאי לנסות עם קומפוננטות צד-שרת וקומפוננטות צד-לקוח כדי להבין טוב יותר את ההבדלים ביניהן: 1. הוסיפו את הודעת ה console.log לכל אחת משלושת הקומפוננטות ושימו לב איפה הודעת ההדפסה מופיעה בכל קומפוננטה. 2. חשבו: למה הודעות ההדפסה שכתבנו בקומפוננטות צד-לקוח מופיעות גם בקונסול של הדפדפן וגם בחלון שורת הפקודה?

ToCode
1 420
קומפוננטות צד-שרת, קומפוננטות צד-לקוח ו State זוכרים את useState שלא עבד לנו בפרק הקודם? בפרק זה נבין למה ונלמד עוד טיפ על הארכיטקטורה של next.js. קומפוננטת צד-שרת בשביל לשפר ביצועים הארכיטקטורה הבסיסית של next.js מחלקת את קומפוננטות ריאקט שאנחנו כותבים לקומפוננטות צד-שרת וקומפוננטות צד-לקוח. קומפוננטות צד-לקוח זה מה שאנחנו מכירים מריאקט "הרגיל" עם תוספת של SSR, כלומר השרת לוקח את הקומפוננטה, הופך אותה ל HTML, שולח את זה לדפדפן יחד עם ה JavaScript של הקומפוננטה שם היא עוברת רינדור נוסף וקוד הטיפול באירועים "מתלבש" על ה HTML שנשלח מהשרת. קומפוננטת צד שרת היא בסך הכל רעיון דומה חוץ מזה שמוותרים על החלק של לשלוח את ה JavaScript לדפדפן ולרנדר את הקומפוננטה שוב בצד הלקוח, כלומר נשארים רק עם HTML שנשלח מהשרת ללקוח. מתוך הארכיטקטורה השונה נגזרות גם היכולות השונות: 1. קומפוננטות צד-לקוח יכולות להשתמש ב State, כיוון שדברים יכולים לקרות ולשנות את מה שמוצג למשתמש. 2. קומפוננטות צד-שרת לא יכולות להשתמש ב State, כי אין להן דרך להוסיף קוד טיפול באירועים. מצד שני קומפוננטות צד-שרת יכולות לגשת בצורה ישירה לבסיס הנתונים או למערכת ההפעלה של השרת, כי הקוד רץ על השרת בלבד. הקומפוננטה page.tsx שכתבנו בשיעור הפתיחה היתה קומפוננטת צד שרת, כי זו ברירת המחדל של קומפוננטות ב next.js. בשביל להוסיף לה State אני צריך להפוך אותה לקומפוננטה צד-לקוח בעזרת הוספת השורה:
"use client";
בראש הקובץ. לדוגמה אכתוב את התוכן הבא בקובץ page.tsx כדי להציג קומפוננטה של מונה לחיצות:
"use client";
import Image from 'next/image'
import { useState } from 'react';


export default function Home() {
  const [count, setCount] = useState(0);
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </main>
  )
}
אפשר גם לפצל את הקומפוננטה לכמה קומפוננטות כדי לשים מספר מונים על המסך:
"use client";
import { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

export default function Home() {
  
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Counter />
      <Counter />
      <Counter />
    </main>
  )
}
שילוב קומפוננטות צד-שרת עם קומפוננטות צד-לקוח בואו נעשה עוד ניסוי, נוציא את Counter לקובץ אחר בשם counter.tsx ואז בקובץ page.tsx נשאיר רק את הקוד הבא:
import Counter from './counter'
export default function Home() {
  
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Counter />
      <Counter />
      <Counter />
    </main>
  )
}
זה אגב יהיה הקוד ב counter.tsx:
"use client";
import { useState } from 'react';

export default () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
כיף לשים לב שאין ל next שום בעיה כשאני משלב קומפוננטות צד-לקוח בתוך קומפוננטות צד-שרת. קומפוננטות צד הלקוח יישלחו לדפדפן וירונדרו גם שם, בעוד שהחלקים שהם קומפוננטות צד שרת ירונדרו רק בצד השרת. מצב הפוך הוא קצת יותר מסובך. ניצור קובץ בשם header.tsx עם התוכן הבא:
export default () => (<h1>Counter Header</h1>);
ונעדכן את counter.tsx כך שיכיל את הקוד:
"use client";
import { useState } from 'react';
import Header from './header';

export default () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Header />
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>
  )
}
למרות ש header.tsx לא כולל את הכותרת use client הוא עדיין קומפוננטת צד-לקוח, בגלל שעשיתי לו import מתוך קומפוננטת צד-לקוח. דרך קלה להיווכח בזה היא להוסיף console.log בתוך הקומפוננטה ולראות אם ההדפסה מופיעה גם בקונסול של הדפדפן וגם בחלון שורת הפקודה, או רק בחלון שורת הפקודה. עכשיו אתם

ToCode
1 420
3. הקוד ב page.tsx יכול להשתמש ב import כדי לטעון קומפוננטות מקבצים אחרים. צרו בקובץ אחר קומפוננטה לתפריט עליון והוסיפו אותה לעמוד עם import. 4. נסו להוסיף לאחת הקומפוננטות קריאה ל useState ושימו לב לשגיאה שמוצגת על העמוד. שחקו עם זה כדי להבין איזה hooks עובדים ואיזה לא.

ToCode
1 420
src="/next.svg"
          alt="Next.js Logo"
          width={180}
          height={37}
          priority
        />
      </div>

      <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
        <a
          href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={\mb-3 text-2xl font-semibold\}>
            Docs{' '}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={\m-0 max-w-[30ch] text-sm opacity-50\}>
            Find in-depth information about Next.js features and API.
          </p>
        </a>

        <a
          href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={\mb-3 text-2xl font-semibold\}>
            Learn{' '}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={\m-0 max-w-[30ch] text-sm opacity-50\}>
            Learn about Next.js in an interactive course with&nbsp;quizzes!
          </p>
        </a>

        <a
          href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={\mb-3 text-2xl font-semibold\}>
            Templates{' '}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={\m-0 max-w-[30ch] text-sm opacity-50\}>
            Explore starter templates for Next.js.
          </p>
        </a>

        <a
          href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          target="_blank"
          rel="noopener noreferrer"
        >
          <h2 className={\mb-3 text-2xl font-semibold\}>
            Deploy{' '}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
          <p className={\m-0 max-w-[30ch] text-sm opacity-50\}>
            Instantly deploy your Next.js site to a shareable URL with Vercel.
          </p>
        </a>
      </div>
    </main>
  )
}
הצעות לתרגול הקובץ layout.tsx כולל הגדרות כלליות לעמוד והקובץ page.tsx כולל את קוד קומפוננטת ריאקט של העמוד. 1. צרו פרויקט next.js חדש והפעילו את שרת הפיתוח. 2. עדכנו את הקוד ב page.tsx כדי שיציג הודעת פתיחה שלכם, ושימו לב איך הקוד מתעדכן בזמן אמת על המסך.