לדלג לתוכן

9.9 דיפלוימנט וארכיטקטורה הרצאה

דיפלוימנט וארכיטקטורה - Deployment and Architecture

בשיעור זה נלמד איך לבנות ולפרוס אפליקציית Next.js לייצור, איך לנהל משתני סביבה, ואיך לתכנן ארכיטקטורה נכונה לפרויקט.


בנייה לייצור - Building for Production

פקודת build

npm run build

הפקודה מבצעת:
- קומפילציה של TypeScript
- אופטימיזציות: minification, tree shaking, code splitting
- יצירת דפים סטטיים (SSG)
- Pre-rendering של Server Components
- ייצור manifest של כל הנתיבים

פלט לדוגמה:

Route (app)                    Size     First Load JS
  /                            5.2 kB   90.3 kB
  /about                       1.2 kB   86.3 kB
  /blog                        2.8 kB   88.0 kB
  /blog/[slug]                 3.1 kB   88.2 kB
  /api/posts                   0 B      0 B
  /dashboard                   4.5 kB   89.6 kB

  First Load JS shared by all  85.1 kB
    chunks/main-abc123.js      60.2 kB
    chunks/pages-def456.js     24.9 kB

Symbols:
  ○  Static
  λ  Dynamic (server-rendered)
  ƒ  Dynamic (uses fetch)
  • סטטי - נבנה בזמן build
  • λ דינמי - נבנה בכל בקשה
  • ƒ דינמי עם fetch

הרצה בייצור

npm run start
  • מריץ את הגרסה שנבנתה
  • אין hot reload או developer tools
  • ביצועים מיטביים

דיפלוי לוורסל - Deploying to Vercel

Vercel היא הפלטפורמה המומלצת לפריסת Next.js (אותה חברה שפיתחה את נקסט).

דיפלוי מ-Git

  1. העלו את הפרויקט ל-GitHub
  2. היכנסו ל-vercel.com והתחברו עם GitHub
  3. בחרו את ה-repository
  4. Vercel מזהה אוטומטית שזה פרויקט Next.js
  5. לחצו Deploy
# אופציה: דיפלוי מ-CLI
npm install -g vercel
vercel

הגדרות בוורסל

  • Build Command: npm run build (אוטומטי)
  • Output Directory: .next (אוטומטי)
  • Install Command: npm install (אוטומטי)
  • משתני סביבה: מוגדרים בהגדרות הפרויקט

דיפלוי אוטומטי

  • כל push ל-main מפעיל דיפלוי לייצור
  • כל push לענף אחר מייצר Preview deployment
  • אפשר לחבר ל-PR של GitHub לקבלת preview URL

משתני סביבה - Environment Variables

סוגי משתנים

# .env.local - משתני סביבה מקומיים (לא עולה ל-git)
DATABASE_URL="file:./dev.db"
AUTH_SECRET="my-secret"
API_KEY="secret-api-key"

# משתנים שזמינים בדפדפן - חייבים להתחיל ב-NEXT_PUBLIC_
NEXT_PUBLIC_API_URL="https://api.example.com"
NEXT_PUBLIC_SITE_URL="https://mysite.com"
  • משתנים רגילים זמינים רק בשרת (Server Components, Server Actions, Route Handlers)
  • משתנים עם NEXT_PUBLIC_ זמינים גם בדפדפן - לא לשים בהם סודות

קבצי סביבה

.env                # ברירת מחדל לכל הסביבות
.env.local          # מקומי - דורס את .env
.env.development    # רק במצב development
.env.production     # רק במצב production
  • סדר עדיפות: .env.local > .env.development/.env.production > .env
  • .env.local לא עולה ל-git (כבר ב-.gitignore של Next.js)

שימוש בקוד

// Server Component - גישה למשתנים רגילים
export default function Page() {
  const dbUrl = process.env.DATABASE_URL;
  const apiKey = process.env.API_KEY;

  return <div>מחובר לבסיס נתונים</div>;
}
// Client Component - רק NEXT_PUBLIC_
"use client";

export default function ApiInfo() {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL;

  return <p>כתובת API: {apiUrl}</p>;
}

ארכיטקטורת פרויקט - Project Architecture

מבנה תיקיות מומלץ

app/
  (auth)/                    # Route Group - דפי אותנטיקציה
    login/
      page.tsx
    register/
      page.tsx
    layout.tsx
  (main)/                    # Route Group - אפליקציה ראשית
    layout.tsx
    page.tsx
    dashboard/
      page.tsx
    settings/
      page.tsx
  api/
    posts/
      route.ts
    users/
      route.ts
  components/                # קומפוננטות משותפות
    ui/                      # קומפוננטות UI בסיסיות
      Button.tsx
      Input.tsx
      Card.tsx
      Modal.tsx
    layout/                  # קומפוננטות מבנה
      Navbar.tsx
      Footer.tsx
      Sidebar.tsx
    forms/                   # קומפוננטות טפסים
      LoginForm.tsx
      PostForm.tsx
  actions/                   # Server Actions
    auth.ts
    posts.ts
    users.ts
  hooks/                     # Custom hooks
    useDebounce.ts
    useLocalStorage.ts
lib/                         # ספריות ועזרים
  prisma.ts
  utils.ts
  constants.ts
  validations.ts
types/                       # טיפוסי TypeScript
  index.ts
  post.ts
  user.ts
prisma/
  schema.prisma
  migrations/
  seed.ts
public/
  images/
  fonts/

עקרונות ארכון

  • הפרדת אחריות: כל קובץ עושה דבר אחד
  • קומפוננטות קטנות: קומפוננטה לא צריכה להיות ארוכה מדי
  • שימוש חוזר: קוד שמשתמשים בו יותר מפעם אחת - לתיקייה משותפת
  • Colocation: קבצים קשורים יושבים קרוב זה לזה

תבנית Feature-Based

לפרויקטים גדולים, מומלץ לארגן לפי פיצ'רים:

app/
  (features)/
    posts/
      components/
        PostCard.tsx
        PostForm.tsx
        PostList.tsx
      actions.ts
      types.ts
      page.tsx
      [id]/
        page.tsx
    users/
      components/
        UserCard.tsx
        UserList.tsx
      actions.ts
      types.ts
      page.tsx
    comments/
      components/
        CommentForm.tsx
        CommentList.tsx
      actions.ts
  components/                # קומפוננטות גלובליות
  lib/                       # ספריות משותפות
  • כל פיצ'ר מכיל את הקומפוננטות, ה-actions, והטיפוסים שלו
  • קל להבין מה שייך למה
  • קל להוסיף או להסיר פיצ'רים

טיפול בשגיאות - Error Handling

שגיאות ברמת הדף

// app/error.tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="min-h-[50vh] flex flex-col items-center justify-center">
      <h2 className="text-2xl font-bold mb-4">משהו השתבש</h2>
      <p className="text-gray-600 mb-4">{error.message}</p>
      <button
        onClick={reset}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        נסה שוב
      </button>
    </div>
  );
}

שגיאות ב-Server Actions

"use server";

interface ActionResult<T = void> {
  success: boolean;
  data?: T;
  error?: string;
}

export async function createPost(
  formData: FormData
): Promise<ActionResult<{ id: number }>> {
  try {
    // ולידציה
    const title = formData.get("title") as string;
    if (!title) {
      return { success: false, error: "כותרת היא שדה חובה" };
    }

    // יצירה
    const post = await prisma.post.create({
      data: { title, content: "", slug: "", authorId: 1 },
    });

    revalidatePath("/posts");
    return { success: true, data: { id: post.id } };
  } catch (error) {
    console.error("Error creating post:", error);
    return { success: false, error: "שגיאה ביצירת הפוסט" };
  }
}

שגיאות גלובליות

// app/global-error.tsx
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <div className="min-h-screen flex items-center justify-center">
          <div className="text-center">
            <h2 className="text-3xl font-bold mb-4">שגיאה קריטית</h2>
            <button
              onClick={reset}
              className="px-6 py-3 bg-blue-500 text-white rounded"
            >
              נסה שוב
            </button>
          </div>
        </div>
      </body>
    </html>
  );
}

ספריית עזר - Utility Library

// lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

// מיזוג className עם Tailwind
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// פורמט תאריך
export function formatDate(date: Date | string): string {
  return new Date(date).toLocaleDateString("he-IL", {
    year: "numeric",
    month: "long",
    day: "numeric",
  });
}

// השהייה (לפיתוח)
export function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// יצירת slug מכותרת
export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/\s+/g, "-")
    .replace(/[^a-z0-9-]/g, "")
    .replace(/-+/g, "-")
    .trim();
}
// lib/validations.ts
export function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

export function validatePassword(password: string): string | null {
  if (password.length < 6) return "הסיסמה חייבת להכיל לפחות 6 תווים";
  if (!/[A-Z]/.test(password)) return "הסיסמה חייבת להכיל אות גדולה";
  if (!/[0-9]/.test(password)) return "הסיסמה חייבת להכיל ספרה";
  return null;
}

מוניטורינג ואנליטיקס - Monitoring and Analytics

לוגינג

// lib/logger.ts
export function log(level: "info" | "warn" | "error", message: string, data?: any) {
  const timestamp = new Date().toISOString();

  if (level === "error") {
    console.error(`[${timestamp}] ERROR: ${message}`, data);
  } else if (level === "warn") {
    console.warn(`[${timestamp}] WARN: ${message}`, data);
  } else {
    console.log(`[${timestamp}] INFO: ${message}`, data);
  }
}

אנליטיקס עם Vercel Analytics

npm install @vercel/analytics
// app/layout.tsx
import { Analytics } from "@vercel/analytics/react";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="he" dir="rtl">
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

ביצועים עם Vercel Speed Insights

npm install @vercel/speed-insights
// app/layout.tsx
import { SpeedInsights } from "@vercel/speed-insights/next";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="he" dir="rtl">
      <body>
        {children}
        <SpeedInsights />
      </body>
    </html>
  );
}

רשימת בדיקות לפני דיפלוי

  • כל משתני הסביבה הוגדרו בפלטפורמת הדיפלוי
  • הרצת npm run build עוברת בהצלחה ללא שגיאות
  • הרצת npm run lint ללא שגיאות
  • בדיקת ביצועים עם Lighthouse (ציון מעל 90)
  • בדיקת מטא-דאטה ו-SEO
  • בדיקת תמונות ופונטים (אופטימיזציה)
  • בדיקת טפסים ו-Server Actions
  • בדיקת אותנטיקציה והרשאות
  • בדיקת responsive design
  • הגדרת דומיין מותאם אישית (אם רלוונטי)

סיכום

  • npm run build בונה את האפליקציה לייצור עם אופטימיזציות
  • Vercel היא הפלטפורמה המומלצת לדיפלוי Next.js
  • משתני סביבה: רגילים לשרת, NEXT_PUBLIC_ לקליינט
  • ארכיטקטורה טובה מחלקת קוד לפי אחריות או פיצ'רים
  • טיפול בשגיאות ברמות שונות: דף, action, גלובלי
  • מוניטורינג ואנליטיקס חשובים למעקב אחרי ביצועים
  • רשימת בדיקות לפני כל דיפלוי מבטיחה איכות