404 Not Found

404 Not Found


nginx

تمارين شاملة — البداية

الآن بعد أن أكملنا الدروس الخمسة في المرحلة الأولى، حان الوقت لربط كل شيء معًا. طلب Charlie من الفريق بناء نسخة أساسية عاملة من MegaShop—بما في ذلك صفحة رئيسية وصفحات فئات وصفحات تفاصيل منتجات، بالإضافة إلى الجلب المسبق لـ SSR وتبديل التخطيط السليم. ستقوم Alice بدور الزبونة لاختبار تجربة المستخدم.

1. ما ستتعلمه


2. قصة حقيقية عن فريق

(1) نقطة الألم: عدم القدرة على دمج المعرفة المتباينة

تعلمت Alice عن التوجيه والمكونات وجلب البيانات، لكنها لا تعرف كيف تجمعها في مشروع كامل. صفحة Bob ليس لها تخطيط، والصفحة تومض بعد تحميل بيانات المنتج. يحتاج Charlie إلى MVP لعرضه على المستثمرين.

(2) حلول شاملة لمشاكل العالم الحقيقي

ادمج كل المفاهيم من المرحلة الأولى في مشروع واحد: توجيه الملفات + نظام التخطيط + الجلب المسبق لـ SSR بـ useFetch + الاستيراد التلقائي للمكونات، وابنِ منصة تجارة إلكترونية عاملة من الصفر.

(3) الفوائد: من الأجزاء إلى المنتجات

بنهاية هذا الفصل، ستملك Alice نسخة أساسية عاملة من MegaShop—بأربع صفحات وتخطيطين وتصيير من جانب الخادم (SSR) مع بيانات منتجات مُحضّرة مسبقًا ومخرجات HTML صديقة لـ SEO.


3. تخطيط المشروع

(1) بنية نسخة MegaShop الأساسية

100%
flowchart TB
    A[MegaShop Phase 1] --> B[Pages]
    A --> C[Layouts]
    A --> D[Components]
    A --> E[API Routes]

    B --> B1[/ → Homepage]
    B --> B2[/products → Product List]
    B --> B3[/products/[id] → Product Detail]
    B --> B4[/about → About Page]

    C --> C1[default → Header + Footer]
    C --> C2[sidebar → Admin Sidebar]

    D --> D1[AppHeader]
    D --> D2[AppFooter]
    D --> D3[ProductCard]

    E --> E1[GET /api/products]
    E --> E2[GET /api/products/:id]

(2) تصميم الصفحات والمسارات

URL ملف الصفحة التخطيط الميزات
/ pages/index.vue default الرئيسية: منتجات مميزة
/products pages/products/index.vue default قائمة المنتجات (جلب مسبق SSR)
/products/[id] pages/products/[id].vue default تفاصيل المنتج (جلب مسبق SSR)
/about pages/about.vue default صفحة حول
/admin pages/admin/index.vue sidebar لوحة الإدارة
404 Page Not Found default 404 مخصص

4. بناء هيكل الصفحات

(1) ▶ مثال: المكون الجذري app.vue

VUE
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: التخطيط الافتراضي

VUE
<!-- layouts/default.vue -->
<template>
  <div class="layout-default">
    <AppHeader />
    <main class="container">
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

الناتج:

TEXT
// Execution Successful

(3) ▶ مثال: تخطيط الشريط الجانبي

VUE
<!-- layouts/sidebar.vue -->
<template>
  <div class="layout-sidebar">
    <AppHeader />
    <div class="main-wrapper">
      <aside class="sidebar">
        <h3>Admin Panel</h3>
        <nav>
          <NuxtLink to="/admin">Dashboard</NuxtLink>
          <NuxtLink to="/admin/products">Products</NuxtLink>
          <NuxtLink to="/admin/orders">Orders</NuxtLink>
        </nav>
      </aside>
      <main class="content">
        <slot />
      </main>
    </div>
  </div>
</template>

الناتج:

TEXT
// Execution Successful

5. مسارات API الأساسية

(1) ▶ مثال: API قائمة المنتجات

TYPESCRIPT
// server/api/products/index.get.ts
interface Product {
  id: number; name: string; price: number
  image: string; category: string; inStock: boolean
}

const mockProducts: Product[] = Array.from({ length: 100 }, (_, i) => ({
  id: i + 1,
  name: `Product ${i + 1}`,
  price: Math.round(Math.random() * 500 * 100) / 100,
  image: `/images/product-${i + 1}.webp`,
  category: ['electronics', 'clothing', 'home'][i % 3],
  inStock: Math.random() > 0.2
}))

export default defineEventHandler((event) => {
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = Number(query.limit) || 12
  const category = query.category as string

  let filtered = mockProducts
  if (category) filtered = filtered.filter(p => p.category === category)

  const start = (page - 1) * limit
  return {
    items: filtered.slice(start, start + limit),
    total: filtered.length,
    page,
    limit
  }
})

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: API تفاصيل المنتج

TYPESCRIPT
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
  const id = Number(getRouterParam(event, 'id'))
  const product = {
    id,
    name: `Premium Product ${id}`,
    price: 299.99,
    image: `/images/product-${id}.webp`,
    category: 'electronics',
    inStock: true,
    description: `High-quality product #${id} with premium features.`,
    reviewCount: Math.floor(Math.random() * 5 * 1000),
    rating: 4.5
  }

  if (id > 100) {
    throw createError({ statusCode: 404, message: 'Product not found' })
  }

  return product
})

الناتج:

TEXT
// Execution Successful

6. تنفيذ الصفحات

(1) ▶ مثال: الجلب المسبق لـ SSR للصفحة الرئيسية

VUE
<!-- pages/index.vue -->
<template>
  <div class="homepage">
    <section class="hero">
      <h1>MegaShop</h1>
      <p>Over 1 million products, shipped worldwide</p>
      <NuxtLink to="/products" class="cta">Shop Now</NuxtLink>
    </section>
    <section class="featured">
      <h2>Featured Products</h2>
      <div class="grid">
        <ProductCard v-for="p in featured" :key="p.id" :product="p" />
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
const { data: featured } = await useFetch('/api/products', {
  query: { limit: 6 },
  transform: (data: any) => data.items
})

useHead({ title: 'MegaShop - Premium E-Commerce' })
</script>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: صفحة تفاصيل المنتج

VUE
<!-- pages/products/[id].vue -->
<template>
  <div v-if="product" class="product-detail">
    <img :src="product.image" :alt="product.name" />
    <div class="info">
      <h1>{{ product.name }}</h1>
      <p class="price">${{ product.price }} USD</p>
      <p>{{ product.description }}</p>
      <p>{{ product.reviewCount }} reviews · {{ product.rating }} stars</p>
      <button @click="addToCart">Add to Cart</button>
    </div>
  </div>
  <div v-else class="not-found">
    <h1>Product Not Found</h1>
    <NuxtLink to="/products">Back to Products</NuxtLink>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)

useHead({
  title: computed(() => product.value ? `${product.value.name} - MegaShop` : 'Not Found')
})

const cart = useState<any[]>('cart', () => [])
function addToCart() {
  if (product.value) cart.value.push(product.value)
}
</script>

الناتج:

TEXT
// Execution Successful

7. قائمة مراجعة الكود

(1) فحص إماهة بيانات SSR

عنصر الفحص الطريقة الصحيحة الأخطاء الشائعة
استخدام useFetch await useFetch() انتظار البيانات نسيان await يؤدي لحالة pending
عرض مصدر SSR HTML يحتوي على بيانات المنتجات فقط <div> فارغ
تمرير Payload العميل لا يكرر الطلبات طلبات API مكررة في Network
route.params استرجاع في المستوى الأعلى من setup استرجاع في onMounted (هذا الخطاف غير متاح في SSR)

(2) فحص صحة الاستيراد التلقائي

عنصر الفحص الطريقة الصحيحة الأخطاء الشائعة
الإشارة للمكونات ProductCard استيراد تلقائي استيراد المكونات يدويًا
Composable useFetch() استخدام مباشر استيراد useFetch يدويًا
useState useState('key') استخدام مباشر استيراد يدوي من 'nuxt/app'
اسم المكون إشارة بناءً على قواعد بادئة المجلد اسم المكون لا يتطابق مع مسار الملف

8. مثال شامل: nuxt.config.ts لنسخة MegaShop الأساسية

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,

  app: {
    head: {
      title: 'MegaShop',
      meta: [
        { name: 'description', content: 'Premium e-commerce with over 1 million products' }
      ]
    }
  },

  modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],

  components: [{ path: '~/components', pathPrefix: false }],

  routeRules: {
    '/': { prerender: true },
    '/products': { swr: 60 },
    '/products/**': { swr: 3600 },
    '/admin/**': { ssr: false }
  }
})

❓ أسئلة شائعة

س لماذا لا تُحدّث البيانات بعد التصيير المسبق للصفحة الرئيسية؟
ج تعيين prerender: true يُولّد HTML ثابت أثناء البناء، لذلك لن تُحدّث البيانات تلقائيًا. ستحتاج لإعادة البناء أو التبديل لاستراتيجية swr.
س لماذا تستخدم صفحة تفاصيل المنتج SSR أحيانًا ولا تستخدمه أحيانًا أخرى؟
ج تحقق مما إذا كان useFetch يُستخدم بشكل صحيح مع await. إذا كان useFetch داخل عبارة شرطية، قد يتخطاه SSR. تأكد من استدعائه في المستوى الأعلى من setup.
س كيف تُفعّل صفحة 404؟
ج يعرض Nuxt 3 تلقائيًا صفحة 404 عندما لا يتطابق أي مسار. يمكنك أيضًا رمي createError({ statusCode: 404 }) على مستوى API.
س ماذا أفعل إذا كان هناك وميض عند تبديل التخطيطات؟
ج التخطيطات تُعاد تصييرها عند التبديل؛ يمكنك تغليف الفتحة في <Transition> لإضافة حركة انتقال، أو استخدام إعداد pageTransition.
س كيف سيتم استبدال البيانات الوهمية في بيئة الإنتاج؟
ج في المرحلة الثانية، سيحل Prisma محل البيانات الوهمية. تنسيق استجابة API الحالي سيبقى متسقًا؛ لاحقًا سنحتاج فقط لتعديل تنفيذ API الخادم.
س كيف أتحقق مما إذا كان SSR يعمل؟
ج استخدم وظيفة "عرض المصدر" في المتصفح (Ctrl+U). إذا كان HTML يتضمن أسماء وأسعار المنتجات، فإن SSR يعمل. إذا كان هناك فقط div فارغ، فهو CSR.

📖ملخص


📝تمارين

  1. تمرين أساسي (الصعوبة: ⭐): أكمل جميع صفحات نسخة MegaShop الأساسية وتأكد من إمكانية الوصول لجميع المسارات.
  2. تمرين متقدم (الصعوبة: ⭐⭐): أضف ميزة فلتر فئات المنتجات، واستخدم watch و useFetch في صفحة قائمة المنتجات لتحديث الصفحة تلقائيًا عند تبديل الفئات.
  3. تحدٍ (الصعوبة: ⭐⭐⭐): أضف صفحة admin/products (تخطيط sidebar + وضع CSR)، نفّذ عرض جدول المنتجات، وقارن الفروقات بين صفحات CSR و SSR عند عرض المصدر.

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%