9.9 דיפלוימנט וארכיטקטורה פתרון
פתרון - דיפלוימנט וארכיטקטורה - Deployment and Architecture¶
פתרון תרגיל 1 - בנייה ובדיקת ייצור¶
- ברגע שה-build עובר, נבדוק את הפלט
- דפים עם
○הם סטטיים - נבנו בזמן build - דפים עם
λהם דינמיים - נבנים בכל בקשה - אם דף שיכול להיות סטטי מסומן כדינמי, בדקו שאין
cache: "no-store"מיותר
בדיקת Lighthouse:
- פתחו את כלי הפיתוח בדפדפן
- לשונית Lighthouse
- הריצו על כל הקטגוריות
- שפרו בהתאם להמלצות
פתרון תרגיל 2 - משתני סביבה¶
# .env.local
DATABASE_URL="file:./dev.db"
AUTH_SECRET="my-super-secret-key-that-is-long-enough"
API_KEY="sk-12345678"
NEXT_PUBLIC_SITE_NAME="האתר שלי"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
// lib/env.ts
// משתני שרת - לא זמינים בדפדפן
function getServerEnv() {
const vars = {
DATABASE_URL: process.env.DATABASE_URL,
AUTH_SECRET: process.env.AUTH_SECRET,
API_KEY: process.env.API_KEY,
};
// ולידציה
for (const [key, value] of Object.entries(vars)) {
if (!value) {
throw new Error(`משתנה סביבה חסר: ${key}`);
}
}
return vars as Record<keyof typeof vars, string>;
}
// משתני קליינט - זמינים בדפדפן
export const publicEnv = {
SITE_NAME: process.env.NEXT_PUBLIC_SITE_NAME || "האתר שלי",
SITE_URL: process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
};
// ייצוא רק לשרת
export const serverEnv = getServerEnv();
// שימוש ב-Server Component
import { serverEnv } from "@/lib/env";
export default function Page() {
const apiKey = serverEnv.API_KEY; // עובד
return <div>מחובר</div>;
}
// שימוש ב-Client Component
"use client";
import { publicEnv } from "@/lib/env";
export default function SiteName() {
return <span>{publicEnv.SITE_NAME}</span>;
}
פתרון תרגיל 3 - ארגון מחדש של פרויקט¶
app/
layout.tsx
page.tsx
not-found.tsx
error.tsx
global-error.tsx
(auth)/
login/page.tsx
register/page.tsx
layout.tsx
(main)/
layout.tsx
dashboard/page.tsx
settings/page.tsx
posts/
page.tsx
[id]/page.tsx
new/page.tsx
components/
PostCard.tsx
PostForm.tsx
PostList.tsx
actions.ts
types.ts
users/
page.tsx
[id]/page.tsx
components/
UserCard.tsx
UserList.tsx
actions.ts
types.ts
api/
posts/route.ts
users/route.ts
components/
ui/
Button.tsx
Input.tsx
Card.tsx
Modal.tsx
Skeleton.tsx
layout/
Navbar.tsx
Footer.tsx
Sidebar.tsx
forms/
FormField.tsx
SubmitButton.tsx
lib/
prisma.ts
utils.ts
validations.ts
constants.ts
env.ts
types/
index.ts
// app/components/ui/Button.tsx
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
loading?: boolean;
}
export default function Button({
variant = "primary",
size = "md",
loading = false,
children,
className,
disabled,
...props
}: ButtonProps) {
const variants = {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200",
danger: "bg-red-500 text-white hover:bg-red-600",
};
const sizes = {
sm: "px-3 py-1 text-sm",
md: "px-4 py-2",
lg: "px-6 py-3 text-lg",
};
return (
<button
className={`rounded font-medium transition ${variants[variant]} ${sizes[size]} ${
disabled || loading ? "opacity-50 cursor-not-allowed" : ""
} ${className || ""}`}
disabled={disabled || loading}
{...props}
>
{loading ? "טוען..." : children}
</button>
);
}
// types/index.ts
export interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: Date;
}
export interface Post {
id: number;
title: string;
content: string;
slug: string;
published: boolean;
createdAt: Date;
author: Pick<User, "id" | "name">;
}
export interface ActionResult<T = void> {
success: boolean;
data?: T;
error?: string;
}
פתרון תרגיל 4 - טיפול בשגיאות מקיף¶
// app/error.tsx
"use client";
import Button from "@/app/components/ui/Button";
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 p-8">
<div className="text-6xl mb-4">!</div>
<h2 className="text-2xl font-bold mb-2">שגיאה</h2>
<p className="text-gray-600 mb-6 text-center max-w-md">
אירעה שגיאה בטעינת הדף. נסו לרענן או לחזור מאוחר יותר.
</p>
<div className="flex gap-3">
<Button onClick={reset}>נסה שוב</Button>
<Button variant="secondary" onClick={() => window.location.href = "/"}>
חזרה לבית
</Button>
</div>
</div>
);
}
// app/global-error.tsx
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="he" dir="rtl">
<body>
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center p-8">
<h1 className="text-4xl font-bold text-red-600 mb-4">שגיאה קריטית</h1>
<p className="text-gray-600 mb-6">
האפליקציה נתקלה בשגיאה חמורה. אנא נסו שוב.
</p>
<button
onClick={reset}
className="px-6 py-3 bg-blue-500 text-white rounded hover:bg-blue-600"
>
טען מחדש
</button>
</div>
</div>
</body>
</html>
);
}
// app/not-found.tsx
import Link from "next/link";
export default function NotFound() {
return (
<div className="min-h-[60vh] flex flex-col items-center justify-center text-center p-8">
<h1 className="text-8xl font-bold text-gray-200">404</h1>
<h2 className="text-2xl font-bold mt-4 mb-2">הדף לא נמצא</h2>
<p className="text-gray-600 mb-6 max-w-md">
הדף שחיפשת לא קיים. יכול להיות שהכתובת שגויה או שהדף הועבר.
</p>
<div className="flex gap-3">
<Link
href="/"
className="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
דף הבית
</Link>
<Link
href="/contact"
className="px-6 py-2 border rounded hover:bg-gray-50"
>
צור קשר
</Link>
</div>
</div>
);
}
// lib/logger.ts
type LogLevel = "info" | "warn" | "error";
function formatMessage(level: LogLevel, message: string, data?: any): string {
const timestamp = new Date().toISOString();
return `[${timestamp}] [${level.toUpperCase()}] ${message}`;
}
export const logger = {
info(message: string, data?: any) {
console.log(formatMessage("info", message), data || "");
},
warn(message: string, data?: any) {
console.warn(formatMessage("warn", message), data || "");
},
error(message: string, error?: any) {
console.error(formatMessage("error", message), error || "");
// כאן אפשר לשלוח ל-Sentry או שירות דומה
},
};
פתרון תרגיל 5 - דיפלוי לוורסל¶
# יצירת repository ב-GitHub
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/user/project.git
git push -u origin main
בוורסל:
1. New Project -> Import Git Repository
2. בחירת ה-repo
3. הגדרת משתני סביבה:
- DATABASE_URL
- AUTH_SECRET
- NEXT_PUBLIC_SITE_NAME
4. לחיצה על Deploy
5. ודאו שה-build עובר ללא שגיאות
פתרון תרגיל 6 - אופטימיזציה ובדיקות סופיות¶
רשימת בדיקות:
[x] npm run build - ללא שגיאות
[x] npm run lint - ללא שגיאות
[x] Lighthouse Performance > 90
[x] Lighthouse Accessibility > 90
[x] Lighthouse Best Practices > 90
[x] Lighthouse SEO > 90
[x] כל התמונות דרך next/image
[x] כל הלינקים דרך next/link
[x] פונטים דרך next/font
[x] Metadata מלא בכל דף
[x] Open Graph tags
[x] responsive design - mobile, tablet, desktop
[x] alt text לכל התמונות
[x] ניגודיות צבעים מספקת
[x] טפסים עם labels
// app/layout.tsx - עם אנליטיקס
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="he" dir="rtl">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
תשובות לשאלות¶
-
ההבדל בין .env.local ל-.env: קובץ
.envעולה ל-git ומכיל ערכי ברירת מחדל שלא רגישים. קובץ.env.localלא עולה ל-git ומכיל ערכים רגישים (סיסמאות, מפתחות API)..env.localדורס את.envכשיש ערכים כפולים. -
למה NEXT_PUBLIC_ מסוכנים למידע רגיש: משתנים עם NEXT_PUBLIC_ מוכנסים לתוך ה-JavaScript bundle שנשלח לדפדפן. כל מי שמסתכל ב-source code של הדף יכול לראות את הערכים. לכן אסור לשים שם מפתחות API, סיסמאות או כל מידע רגיש.
-
יתרון Feature-Based: כל הקוד הקשור לפיצ'ר מסוים נמצא במקום אחד. קל למצוא קוד רלוונטי. קל להוסיף או להסיר פיצ'רים שלמים. מפחית תלויות בין חלקים שונים. מתאים לצוותים שכל אחד אחראי על פיצ'ר.
-
global-error.tsx לעומת error.tsx:
error.tsxתופס שגיאות בתוך ה-layout - הלייאאוט עצמו עדיין מוצג.global-error.tsxתופס שגיאות בלייאאוט הראשי עצמו - כשהלייאאוט נשבר. global-error חייב להכיל תגיות html ו-body כי הלייאאוט לא זמין. -
מה Vercel עושה שונה: Vercel בנויה ספציפית ל-Next.js ותומכת ב-SSR, ISR, Middleware, Edge Functions ו-Image Optimization מהקופסה. בנוסף, היא מספקת CDN גלובלי, Preview deployments לכל PR, אנליטיקס, ו-automatic scaling. ב-hosting רגיל צריך להגדיר את כל זה ידנית.