بناء معرض صور احترافي باستخدام Next.js وواجهة Pexels API ومكتبة Chakra UI

دقائق القراءة: 25
في هذا المقال، سنخوض رحلة شيقة لبناء معرض صور تفاعلي واحترافي باستخدام إطار العمل Next.js. سنستفيد من قوة واجهة برمجة التطبيقات Pexels API لجلب مجموعة واسعة من الصور عالية الجودة، وسنعتمد على مكتبة Chakra UI v1، المعروفة بكونها مكتبة مكونات مرنة وسهلة الوصول، لتصميم واجهة المستخدم. علاوة على ذلك، سنتعمق في كيفية استخدام مكون الصور الخاص بـ Next.js (Next.js Image Component) لتحسين أداء الصور وجعلها أكثر كفاءة، وهو أمر بالغ الأهمية لتجربة المستخدم وتحسين محركات البحث (SEO).

إذا كنت تفضل الانتقال مباشرة إلى الكود المصدري، يمكنك الاطلاع على مستودع GitHub الخاص بالمشروع من هنا. ولرؤية النسخة المنشورة من المعرض، تفضل بزيارة: https://next-image-gallery.vercel.app/.

المفاهيم والتقنيات التي سنتناولها:

  • كيفية تثبيت واستخدام مكتبة Chakra UI v1 مع Next.js.
  • طرق جلب البيانات من واجهات برمجة التطبيقات (APIs) في Next.js.
  • الاستفادة القصوى من مكون الصور Next.js Image Component لتحسين الأداء.
  • إعداد المسارات الديناميكية (Dynamic Routes) في Next.js.

سنبدأ الآن رحلتنا التقنية.

المتطلبات الأساسية لبدء المشروع

قبل الغوص في تفاصيل البناء، من الضروري أن تكون لديك بعض المعرفة الأساسية لضمان سير العمل بسلاسة:

  • إلمام جيد بلغات تطوير الويب الأساسية: HTML، CSS، وJavaScript.
  • فهم مبادئ React ومعرفة أساسية بإطار العمل Next.js.
  • تثبيت بيئة التشغيل Node.js ومدير الحزم npm (أو yarn) على جهازك.
  • محرر أكواد تفضله (مثل VS Code).
  • أدوات مطوري React (React Dev Tools) للمتصفح (اختياري، لكنها مفيدة جدًا).

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

إعداد وتثبيت مشروع Next.js

للبدء، سنستخدم أداة Create Next App لتهيئة مشروع Next.js بسرعة وفعالية. افتح سطر الأوامر (terminal) في المجلد الجذر لمشروعك ونفّذ الأوامر التالية:

npx create-next-app next-image-gallery
cd next-image-gallery
npm run dev

سيقوم الأمر الأخير، npm run dev، بتشغيل خادم التطوير على المنفذ 3000 بجهازك. يمكنك الآن التوجه إلى المتصفح وزيارة العنوان http://localhost:3000. ستلاحظ ظهور صفحة الترحيب الافتراضية لـ Next.js:

صفحة الترحيب الافتراضية لتطبيق Next.js على المنفذ 3000

بعد ذلك، سنقوم بتثبيت مكتبة Chakra UI التي ستساعدنا في بناء واجهة المستخدم. نفّذ الأمر التالي في سطر الأوامر:

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion @chakra-ui/icons

الخطوة التالية تتضمن تنظيف الكود النموذجي الذي تم إنشاؤه بواسطة create-next-app وتكوين المشروع لاستخدام Chakra UI. قم بحذف مجلدات styles وpages/api. ثم، حدّث ملف pages/_app.js ليصبح كالتالي:

// pages/_app.js
import { ChakraProvider } from "@chakra-ui/react";

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

export default MyApp;

بعد ذلك، قم بتعديل ملف pages/index.js على النحو التالي:

// pages/index.js
import Head from "next/head";

export default function Home() {
  return (
    <div>
      <Head>
        <title>NextJS Image Gallery</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
    </div>
  );
}

عد إلى http://localhost:3000. ستلاحظ أن التطبيق أصبح فارغًا، لكن العنوان في المتصفح قد تغير إلى NextJS Image Gallery. يمكنك الآن إغلاق خادم التطوير.

الحصول على مفتاح Pexels API

لاستعراض الصور في معرضنا، سنعتمد على Pexels API، وهي واجهة برمجة تطبيقات مجانية تمامًا تتيح لك جلب صور عالية الجودة. لاستخدامها، ستحتاج إلى إنشاء مفتاح API خاص بك لمصادقة طلباتك. تتيح لك Pexels API إجراء ما يصل إلى 200 طلب في الساعة و20,000 طلب شهريًا، وهي حدود كافية لمعظم المشاريع.

  1. إنشاء حساب Pexels: توجه إلى https://www.pexels.com/join-consumer/ وقم بإنشاء حساب جديد.

    صفحة إنشاء حساب جديد على Pexels

    بعد ملء بياناتك، ستحتاج إلى تأكيد حسابك عبر البريد الإلكتروني قبل أن تتمكن من طلب مفتاح API.

  2. طلب مفتاح API: بعد تأكيد حسابك، انتقل إلى https://www.pexels.com/api/new/ واملأ التفاصيل المطلوبة لمفتاح API جديد، ثم انقر على زر “Request API Key”.

    نموذج طلب مفتاح Pexels API

    تذكر دائمًا الالتزام بإرشادات استخدام API.

  3. نسخ مفتاح API: ستظهر لك صفحة تحتوي على مفتاح API الخاص بك. قم بنسخه.

    صفحة عرض مفتاح Pexels API

  4. تخزين مفتاح API بأمان: في المجلد الجذر لمشروعك، أنشئ ملفًا جديدًا باسم .env.local لتخزين مفتاح API بشكل آمن. يمكنك استخدام الأمر التالي:
    touch .env.local
    

    داخل ملف .env.local، قم بإنشاء متغير بيئة جديد باسم NEXT_PUBLIC_PEXELS_API_KEY والصق مفتاح API الذي نسخته:

    NEXT_PUBLIC_PEXELS_API_KEY = 'YOUR_PEXELS_API_KEY_HERE'
    

    ملاحظة هامة: Next.js يدعم تحميل متغيرات البيئة من ملف .env.local إلى process.env. افتراضيًا، تكون هذه المتغيرات متاحة فقط في بيئة Node.js (على الخادم). ولكن باستخدام البادئة NEXT_PUBLIC_، يتم الكشف عن المتغير للمتصفح (العميل)، مما يجعله متاحًا للاستخدام في كود الواجهة الأمامية. يمكنك معرفة المزيد حول هذا الموضوع في توثيقات Next.js.

إضافة عنوان جذاب إلى المعرض

في هذا الجزء، سنقوم بإضافة عنوان رئيسي لمعرض الصور الخاص بنا، مستخدمين مكونات Chakra UI لتصميم أنيق ومتجاوب.

  1. إضافة المكون Box:

    قم باستيراد وإضافة المكون Box إلى ملف index.js على النحو التالي:

    // pages/index.js
    import Head from "next/head";
    import { Box } from "@chakra-ui/react";
    
    export default function Home() {
      return (
        <div>
          <Head>
            <title>NextJS Image Gallery</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <Box overflow="hidden" bg="purple.100" minH="100vh">
          </Box>
        </div>
      );
    }
    

    الآن، توجه إلى http://localhost:3000. ستلاحظ أن خلفية تطبيقك أصبحت بلون أرجواني فاتح.

    صفحة فارغة بخلفية أرجوانية فاتحة بعد إضافة مكون Box

    شرح الخصائص المستخدمة:

    • bg="purple.100": هذه الخاصية هي اختصار لـ background في Chakra UI. تعيينها على purple.100 يمنح التطبيق خلفية أرجوانية فاتحة. الرقم بعد اسم اللون (مثل 100) يمثل درجة اللون؛ حيث 50 هو الأفتح و900 هو الأغمق.

    إليك توضيح لدرجات الألوان من توثيقات Chakra UI:
    درجات اللون الأرجواني في Chakra UI من 50 إلى 900

    • minH="100vh": تحدد هذه الخاصية أن يكون الحد الأدنى لارتفاع التطبيق 100% من ارتفاع نافذة العرض (viewport height). minH هو اختصار لـ min-height.
    • overflow="hidden": تُستخدم هذه الخاصية للتخلص من أشرطة التمرير الزائدة في حال تجاوز المحتوى للعنصر الأب، مما يضمن مظهرًا نظيفًا.
  2. إضافة مكونات Text وContainer:

    لإضافة العنوان، سنستخدم مكوني Text وContainer من Chakra UI. قم بتعديل استيراد Box في index.js ليشمل هذه المكونات:

    import { Box, Container, Text } from "@chakra-ui/react";
    

    الآن، أضف المكون Container داخل المكون Box:

    <Box overflow="hidden" bg="purple.100" minH="100vh">
          <Container>
          </Container>
    </Box>
    

    لن تلاحظ أي تغيير مرئي مباشر في تطبيقك، ولكن المكون Container قد أضاف بعض الحشوة الأفقية (horizontal padding) التي ستصبح أكثر وضوحًا عند إضافة مكون Text.

    أضف الكود التالي داخل المكون Container:

    <Container>
          <Text
            color="pink.800"
            fontWeight="semibold"
            mb="1rem"
            textAlign="center"
            textDecoration="underline"
            fontSize={["4xl", "4xl", "5xl", "5xl"]}
          >
            NextJS Image Gallery
          </Text>
    </Container>
    

    إليك كيف سيبدو عنوان تطبيقك الآن:

    عنوان NextJS Image Gallery مع تنسيقات Chakra UI

    تحليل خصائص العنوان:

    • color="pink.800": تحدد لون النص باللون الوردي الداكن.
    • fontWeight="semibold": تضبط سمك الخط ليكون شبه غامق.
    • mb="1rem": اختصار لـ margin-bottom، وتحدد هامشًا سفليًا بمقدار 1rem (أي 16 بكسل).
    • textAlign="center": توسيط النص أفقيًا.
    • textDecoration="underline": تضيف خطًا تحت النص.
    • fontSize={["4xl", "4xl", "5xl", "5xl"]}: تحدد حجم الخط.

    قد تتساءل لماذا يتم تمرير أربع قيم لـ fontSize كـ array داخل أقواس متعرجة {}؟
    تُستخدم الأقواس {} في JSX لتفسير التعبير بداخلها كـ JavaScript. هنا، يتم استخدام array لتمرير قيم fontSize، وهو اختصار للاستعلامات الإعلامية (media queries) في Chakra UI. يتم تمرير القيم في مصفوفة لجعل النص متجاوبًا (responsive) وتغيير حجم الخط وفقًا لأحجام الشاشات المختلفة.

    const breakpoints = {
      sm: "30em",
      md: "48em",
      lg: "62em",
      xl: "80em",
    }
    

    تتبع هذه الطريقة نهج “الجوال أولاً” (mobile-first)، حيث تكون القيمة الأولى للأجهزة الأصغر، والقيمة الأخيرة لأجهزة سطح المكتب. هذا يعني أن font-size يتغير بناءً على نقطة التوقف (breakpoint) المحددة. يمكنك قراءة المزيد حول هذا الموضوع في توثيقات Chakra UI.

    الكود أعلاه سيولد CSS مشابهًا لهذا:

    .css-px6f4t {
          text-align: center;
          -webkit-text-decoration: underline;
          text-decoration: underline;
          font-size: 2.25rem;
          color: #702459;
          font-weight: 600;
          margin-bottom: 1rem;
        }
        @media screen and (min-width: 30em) {
          .css-px6f4t {
            font-size: 2.25rem;
          }
        }
        @media screen and (min-width: 48em) {
          .css-px6f4t {
            font-size: 3rem;
          }
        }
        @media screen and (min-width: 62em) {
          .css-px6f4t {
            font-size: 3rem;
          }
        }
    

    إليك مقارنة لأحجام العناوين المختلفة على شاشات متعددة كما تظهر في Polypane:

    مقارنة حجم العنوان على أجهزة مختلفة باستخدام Polypane

جلب البيانات من واجهة Pexels API

بعد الحصول على مفتاح API، حان الوقت لكتابة الكود اللازم لجلب الصور. سنقوم بإنشاء ملف منفصل لتنظيم دوال جلب البيانات.

  1. إنشاء ملف api.js:

    في المجلد الجذر لمشروعك، أنشئ مجلدًا باسم lib، وداخله أنشئ ملفًا باسم api.js. يمكنك استخدام الأوامر التالية في سطر الأوامر:

    mkdir lib
    cd lib
    touch api.js
    

    عنوان URL الأساسي لـ Pexels API للصور هو: https://api.pexels.com/v1/.
    توفر Pexels API ثلاث نقاط نهاية (endpoints) رئيسية:

    • /curated: لجلب صور منتقاة بعناية بواسطة فريق Pexels في الوقت الفعلي.
    • /search: للبحث عن الصور بناءً على استعلام محدد.
    • /photos/:id: للحصول على صورة واحدة محددة باستخدام معرفها (ID).

    في البداية، سنستخدم نقطة النهاية /curated لعرض الصور المنتقاة على الصفحة الرئيسية لتطبيقنا.

  2. إضافة دالة getCuratedPhotos:

    أضف الكود التالي إلى ملف api.js:

    const API_KEY = process.env.NEXT_PUBLIC_PEXELS_API_KEY;
    
    export const getCuratedPhotos = async () => {
      const res = await fetch(
        `https://api.pexels.com/v1/curated?page=11&per_page=18`,
        {
          headers: {
            Authorization: API_KEY,
          },
        }
      );
      const responseJson = await res.json();
      return responseJson.photos;
    };
    

    شرح الكود أعلاه:

    • نبدأ بتعريف متغير API_KEY الذي يصل إلى مفتاح API المخزن في متغير البيئة NEXT_PUBLIC_PEXELS_API_KEY باستخدام process.env.
    • ثم ننشئ دالة غير متزامنة (async function) باسم getCuratedPhotos() تستخدم طريقة fetch() لجلب البيانات من API.
    • إذا نظرت عن كثب إلى URL الخاص بالطلب، ستلاحظ أننا أضفنا ?page=11&per_page=18 بعد نقطة النهاية /curated. هذه هي معاملات اختيارية (optional parameters) يمكنك تمريرها إلى نقطة النهاية /curated كسلاسل استعلام (query strings). هنا، page=11 تعني جلب الصفحة الحادية عشرة، وper_page=18 تعني جلب 18 صورة لكل صفحة. يمكنك إزالة هذه المعاملات الاختيارية، وفي هذه الحالة ستعيد API 15 صورة من الصفحة الأولى. يمكنك الحصول على ما يصل إلى 80 صورة في طلب واحد.
    • يتم تمرير مفتاح Pexels API في حقل Authorization ضمن headers الطلب.
    • تقوم الدالة res.json() بتحليل الاستجابة بتنسيق JSON.
    • يحتوي الكائن responseJson على حقول مثل page وper_page وغيرها، والتي لا يستخدمها تطبيقنا حاليًا. لذلك، يتم إرجاع حقل photos فقط من الاستجابة، والذي يبدو كالتالي:
    [
          {
            id: 4905078,
            width: 7952,
            height: 5304,
            url: "https://www.pexels.com/photo/ocean-waves-under-blue-sky-4905078/",
            photographer: "Nick Bondarev",
            photographer_url: "https://www.pexels.com/@nick-bondarev",
            photographer_id: 2766954,
            src: {
              original: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg",
              large2x: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
              large: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
              medium: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=350",
              small: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=130",
              portrait: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
              landscape: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
              tiny: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280",
            },
            liked: false,
          },
    ];
    

    في حقل src، يتم توفير العديد من تنسيقات الصور المختلفة للاختيار من بينها. في هذا الدرس، سنستخدم صورًا من نوع portrait على صفحتنا الرئيسية، ولكن يمكنك استكشاف التنسيقات الأخرى بحرية. مع تطور تطبيقنا، سنقوم بكتابة دوال للبحث عن الصور والحصول على صورة واحدة محددة في ملف api.js. في الوقت الحالي، سنستخدم هذه الدالة لعرض الصور على صفحتنا الرئيسية.

عرض الصور على الصفحة الرئيسية

بعد أن أنشأنا الدالة اللازمة لجلب البيانات، حان الوقت لعرض هذه الصور على صفحتنا.

  1. استيراد دالة getCuratedPhotos():

    أولاً، قم باستيراد الدالة getCuratedPhotos() في ملف index.js:

    import Head from "next/head";
    import { Box, Container, Text } from "@chakra-ui/react";
    import { getCuratedPhotos } from "../lib/api";
    
  2. استخدام getServerSideProps() لجلب البيانات:

    سنستخدم دالة getServerSideProps() المتاحة في Next.js لجلب البيانات من Pexels API وحقنها مباشرة في صفحتنا قبل عرضها. هذه الدالة تُنفذ على جانب الخادم (server-side)، مما يجعل المحتوى جاهزًا عند تحميل الصفحة الأولى، وهو أمر مفيد جدًا لتحسين محركات البحث (SEO) وتجربة المستخدم. يمكنك قراءة المزيد حول getServerSideProps() في توثيقات Next.js.

    أضف الكود التالي في نهاية ملف index.js:

    export async function getServerSideProps() {
          const data = await getCuratedPhotos();
          return {
            props: { data },
          };
        }
    

    تقوم هذه الدالة غير المتزامنة باستخدام getCuratedPhotos() لجلب الصور من Pexels API وتخزينها في المتغير data. ثم يتم توفير هذا المتغير كخاصية (prop) للمكون Home.

    لجعل data متاحة كمُدخل للمكون Home، قم بتعديل تعريف الدالة Home كالتالي:

    export default function Home({ data }) {
          // ...
        }
    

    أعد تشغيل خادم التطوير الخاص بك. يمكنك الآن داخل المكون Home طباعة محتوى data إلى وحدة التحكم (console) للتحقق:

    export default function Home({ data }) {
          console.log(data);
          return (
            // ...
          );
        }
    

    توجه إلى http://localhost:3000/ وافتح وحدة تحكم المطور (بالضغط على CTRL + Shift + J في Chrome أو CTRL + Shift + K في Firefox). ستشاهد البيانات التي تم جلبها:

    عرض البيانات المجلوبة من Pexels API في وحدة تحكم المتصفح

  3. إدارة حالة الصور باستخدام useState():

    قم بإزالة سطر console.log(). ثم أضف الكود التالي في الجزء العلوي من ملف index.js لاستيراد الخطاف useState() من مكتبة react:

    import React, { useState } from "react";
    

    سنقوم بتخزين البيانات التي تم جلبها من Pexels API في حالة (state) باسم photos. أضف الكود التالي قبل عبارة return في دالة Home:

    const [photos, setPhotos] = useState(data);
    
  4. عرض الصور باستخدام عنصر <img> (النهج الأولي):

    لعرض الصور، سنقوم بعمل حلقة تكرارية (map) على مصفوفة photos ونمرر pic.src.original إلى خاصية src لعنصر <img>. أضف الكود التالي بعد مكون Container:

    {photos.map((pic) => (
          <img src={pic.src.original} width="500" height="500" />
        ))}
    

    سيبدو تطبيقك الآن بهذا الشكل:

    عرض الصور الأولية باستخدام عنصر img بدون تحسين

    مشكلة الأداء مع عنصر <img> التقليدي:
    بصرف النظر عن أن الصور لا يتم تغيير حجمها بشكل صحيح، هناك مشكلة أخرى خطيرة في استخدامنا لعنصر <img> التقليدي لعرض الصور.

    توجه إلى http://localhost:3000/ وافتح أدوات المطور، ثم انتقل إلى تبويب “الشبكة” (Network tab) (Ctrl + Shift + E في Firefox وCtrl + Shift + J في Chrome). ستبدو فارغة في البداية:

    علامة تبويب الشبكة (Network tab) في أدوات المطور

    الآن، أعد تحميل الصفحة. ستلاحظ أن تبويب الشبكة قد امتلأ بالبيانات:

    تحميل ملف صورة واحد كبير الحجم في تبويب الشبكة

    كما ترى في الصورة أعلاه، يبلغ حجم الملف المطلوب أكثر من 11 ميجابايت، وهذا لملف صورة واحد فقط! يمكن أن تتراوح الأحجام من 10 إلى 100 ميجابايت أو أكثر بناءً على جودة الصورة. تخيل أن لديك 80 صورة على الصفحة الرئيسية لتطبيقك. هل من المنطقي نقل حوالي 800 ميجابايت من الملفات في كل مرة يزور فيها شخص معرضك أو موقعك الإلكتروني؟ بالتأكيد لا.

    طلبات متعددة لصور كبيرة الحجم

    لهذا السبب، يتم اليوم تقديم معظم الصور على الويب بتنسيق WebP. يقلل هذا التنسيق بشكل كبير من حجم الصورة، وبالكاد يمكنك اكتشاف أي فرق بصري. لذا، نحتاج إلى تغيير تنسيق الصورة إلى webp، ولكن السؤال هو: كيف؟ هل تحتاج إلى القيام بذلك يدويًا؟ إذا كان الأمر كذلك، ألن يكون ذلك مضيعة للوقت ومجهدًا؟

  5. تحسين الصور باستخدام مكون Next.js Image:

    لحسن الحظ، لا تحتاج إلى القيام بذلك يدويًا! يأتي Next.js في إصداره العاشر وما بعده بدعم مدمج لتحسين الصور باستخدام مكون Image. يمكنك قراءة المزيد حول هذا التحديث في توثيقات Next.js Image.

    لذا، دعنا نستبدل عنصر <img> بمكون Next.js Image. أولاً، قم باستيراد هذا المكون داخل ملف index.js الخاص بك:

    import Image from "next/image";
    

    ولكن قبل استخدام هذا المكون في الكود، نحتاج إلى إخبار Next.js بأن صورنا تأتي من مصدر خارجي، مثل Pexels. أوقف خادم التطوير الخاص بك وأنشئ ملف next.config.js بتشغيل الأمر التالي:

    touch next.config.js
    

    أضف الكود التالي إلى ملف next.config.js:

    module.exports = {
          images: {
            domains: ["images.pexels.com"],
          },
        };
    

    هذا كل ما في الأمر. هناك تكوينات أخرى مثل path وimageSizes وdeviceSizes وما إلى ذلك يمكنك إضافتها في حقل images. ولكن في هذا الدرس، سنتركها كقيم افتراضية. يمكنك قراءة المزيد حول التكوين في توثيقات Next.js Image.

    الآن، استبدل عنصر <img> بمكون Image ومرر الخصائص (props) كما هو موضح أدناه:

    {photos.map((pic) => (
          <Image src={pic.src.portrait} height={600} width={400} alt={pic.url} />
        ))}
    

    كما ناقشنا سابقًا، توفر Pexels API تنسيقات أو أحجامًا مختلفة لنفس الصورة، مثل portrait وlandscape وtiny وما إلى ذلك، ضمن حقل src. يستخدم هذا الدرس صور portrait على الصفحة الرئيسية، ولكنك حر في استكشاف الأحجام الأخرى.

    مثال على حقل src:

    src: {
          original: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg",
          large2x: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
          large: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
          medium: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=350",
          small: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&h=130",
          portrait: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
          landscape: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
          tiny: "https://images.pexels.com/photos/4905078/pexels-photo-4905078.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280",
        }
    

    كما ترى في حقل src النموذجي أعلاه، يبلغ عرض تنسيق portrait للصورة 800 بكسل وارتفاعه 1200 بكسل. لكن هذا الحجم كبير جدًا للعرض على صفحة الويب، لذلك سنقوم بتقليصه عن طريق قسمة العرض والارتفاع على 2. لذا، تم تمرير 600 للارتفاع و400 للعرض لمكون Image.

    أعد تشغيل خادم التطوير الخاص بك وتوجه إلى http://localhost:3000/. ستلاحظ أن التطبيق نفسه يبدو تمامًا كما كان. ولكن هذه المرة، إذا فتحت تبويب الشبكة وأعدت تحميل الصفحة، سترى شيئًا سحريًا حقًا! صورك الآن بتنسيق webp، وقد تم تقليل أحجامها بشكل كبير.

    تبويب الشبكة يظهر الصور المحسنة بتنسيق WebP وأحجامها المخفضة

    التحميل الكسول (Lazy Loading) مع مكون Next.js Image:
    لقد أضاف مكون Next.js Image أيضًا ميزة التحميل الكسول (lazy loading) للصور تلقائيًا. إليك مثال لشرح كيفية ولماذا يجب عليك استخدام التحميل الكسول إذا لم تكن مألوفًا به:

    على الرغم من أن الصور أصبحت الآن بتنسيق webp، هل من الضروري تحميل جميع الصور في كل مرة يزور فيها شخص موقعك الإلكتروني؟ وإذا زار الزائر الصفحة وغادر دون التمرير، فهل من المنطقي تحميل الصور الموجودة في أسفل الصفحة؟

    لا توجد حاجة لتحميل الصور التي لن يراها المستخدم أو الزائر في معظم الحالات. وهنا يأتي دور التحميل الكسول لإنقاذ الموقف. فهو يؤخر طلبات الصور إلى الوقت الذي تكون فيه مطلوبة، أو في هذه الحالة، عندما تظهر الصور في مجال الرؤية (viewport). هذا يساعد بشكل كبير في تقليل وزن الصفحة الأولي ويزيد من أداء الموقع.

    إذا توجهت إلى http://localhost:3000/ وقمت بالتمرير عبر جميع الصور، فسترى أن الصور التي ليست في مجال الرؤية لا يتم تحميلها في البداية. ولكن كلما قمت بالتمرير لأسفل، يتم نقلها وتحميلها.

    عرض توضيحي لميزة التحميل الكسول للصور في Next.js

    بشكل افتراضي، تكون قيمة الخاصية layout لمكون Image هي intrinsic، مما يعني أن الصورة ستقوم بتقليص الأبعاد لأحجام الشاشات الأصغر ولكنها ستحافظ على الأبعاد الأصلية لأحجام الشاشات الأكبر. هناك العديد من الخصائص الأخرى التي يمكنك تمريرها إلى مكون Image لتعديله بشكل أكبر. يمكنك قراءة المزيد عنها في توثيقات Next.js Image.

تنسيق وعرض الصور باستخدام Chakra UI

لتنسيق الصور بشكل جذاب ومنظم، سنستخدم مكون Wrap من Chakra UI. هذا المكون هو مكون تخطيط (layout component) يضيف مسافة محددة بين عناصره الفرعية (الصور في حالتنا)، ويقوم بتغليفها تلقائيًا (wraps) إذا لم يكن هناك مساحة كافية لاستيعابها جميعًا في سطر واحد.

  1. استيراد Wrap وWrapItem:

    قم باستيراد Wrap وWrapItem من Chakra UI عن طريق تعديل سطر الاستيراد:

    import { Box, Container, Text, Wrap, WrapItem } from "@chakra-ui/react";
    

    يحتوي WrapItem على العناصر الفرعية الفردية (الصور)، بينما يحيط Wrap بجميع مكونات WrapItem.

  2. تعديل عرض الصور باستخدام Wrap:

    عدّل التعبير الخاص بعرض الصور ليصبح كالتالي:

    <Wrap px="1rem" spacing={4} justify="center">
          {photos.map((pic) => (
            <Image src={pic.src.portrait} height={600} width={400} alt={pic.url} />
          ))}
    </Wrap>
    

    شرح الخصائص في الكود أعلاه:

    • px="1rem": هي خاصية اختصار لـ padding-left وpadding-right. تضيف هذه الخاصية حشوة أفقية بمقدار 1rem.
    • spacing={4}: تطبق تباعدًا بين كل عنصر فرعي. سيظهر هذا التباعد بوضوح بمجرد تغليف كل صورة بـ WrapItem.
    • justify="center": تقوم بمحاذاة الصور في المنتصف أفقيًا.

    عرض الصور داخل مكون Wrap بدون WrapItem

  3. تغليف الصور بـ WrapItem وإضافة التنسيقات:

    الآن، قم بتغليف كل صورة بمكون WrapItem. أضف الكود التالي داخل تعبير JavaScript:

    <Wrap px="1rem" spacing={4} justify="center">
          {photos.map((pic) => (
            <WrapItem
              key={pic.id}
              boxShadow="base"
              rounded="20px"
              overflow="hidden"
              bg="white"
              lineHeight="0"
              _hover={{ boxShadow: "dark-lg" }}
            >
              <Image src={pic.src.portrait} height={600} width={400} alt={pic.url} />
            </WrapItem>
          ))}
    </Wrap>
    

    شرح الخصائص الممررة إلى WrapItem:

    • key={pic.id}: تُعطي كل صورة مفتاحًا فريدًا حتى يتمكن React من التمييز بين العناصر الفرعية أو الصور.
    • boxShadow="base": تضيف ظلًا أساسيًا إلى WrapItem.
    • rounded="20px": تضيف نصف قطر حدود (border-radius) بمقدار 20px، مما يجعل زوايا الصورة مستديرة.
    • overflow="hidden": تضمن عدم تجاوز الصورة لحدود WrapItem، مما يحافظ على شكل الزوايا المستديرة.
    • bg="white": تضيف خلفية بيضاء إلى WrapItem.
    • lineHeight="0": تضبط خاصية line-height على صفر، وهي مفيدة أحيانًا لتجنب المسافات الإضافية حول الصور.
    • _hover={{ boxShadow: "dark-lg" }}: تغير خاصية boxShadow إلى ظل أغمق (dark-lg) عند مرور مؤشر الفأرة فوق الصورة، مما يضيف تأثيرًا بصريًا جذابًا.

    تأثير الظل عند التمرير فوق الصور في معرض Next.js

    ستلاحظ أن خاصية spacing={4} قد أصبحت فعالة الآن بعد أن أضفنا WrapItem للصور، مما يوفر تباعدًا متناسقًا بينها.

إضافة وظيفة البحث إلى المعرض

الخطوة التالية هي تمكين المستخدمين من البحث عن الصور وعرض النتائج لهم. لتحقيق ذلك، سنستخدم نقطة النهاية /search في Pexels API.

  1. إنشاء دالة getQueryPhotos():

    في ملف lib/api.js، أنشئ دالة جديدة باسم getQueryPhotos() للبحث عن الصور بناءً على مدخلات المستخدم:

    export const getQueryPhotos = async (query) => {
          const res = await fetch(
            `https://api.pexels.com/v1/search?query=${query}`,
            {
              headers: {
                Authorization: API_KEY,
              },
            }
          );
          const responseJson = await res.json();
          return responseJson.photos;
        };
    

    تتشابه دالة getQueryPhotos() أعلاه مع دالة getCuratedPhotos()، ولكن هنا أضفنا معامل query إلى الدالة وقمنا بتعديل نقطة نهاية API لتضمين هذا الاستعلام:

    `https://api.pexels.com/v1/search?query=${query}`
    
  2. استيراد دالة getQueryPhotos():

    قم باستيراد دالة getQueryPhotos() في ملف index.js:

    import { getCuratedPhotos, getQueryPhotos } from "../lib/api";
    
  3. بناء نموذج البحث باستخدام Chakra UI:

    الآن، سننشئ نموذجًا لأخذ مدخلات المستخدم والبحث بناءً عليها. سنقوم باستيراد واستخدام مكونات Input، IconButton، InputRightElement، وInputGroup من Chakra UI لإنشاء هذا النموذج.

    عدّل استيراد Chakra UI وأضف استيرادًا لـ SearchIcon:

    import {
          Box,
          Container,
          Text,
          Wrap,
          WrapItem,
          Input,
          IconButton,
          InputRightElement,
          InputGroup,
        } from "@chakra-ui/react";
        import { SearchIcon } from "@chakra-ui/icons";
    

    أضف الكود التالي لنموذج الإدخال داخل مكون Container في ملف index.js:

    <InputGroup pb="1rem">
          <Input placeholder="Search for Apple" variant="ghost" />
          <InputRightElement
            children={
              <IconButton aria-label="Search" icon={<SearchIcon />} bg="pink.400" color="white" />
            }
          /
    </InputGroup>
    

    شرح المكونات:

    • InputGroup: يُستخدم لتجميع مكوني Input وInputRightElement. الخاصية pb هي اختصار لـ padding-bottom.
    • Input: هو حقل الإدخال حيث سيقوم المستخدمون بكتابة استعلاماتهم. يحتوي على نص توضيحي (placeholder) “Search for Apple”.
    • InputRightElement: يُستخدم لإضافة عنصر إلى يمين مكون Input. هنا، تم تمرير زر أيقونة (IconButton) مع أيقونة البحث إلى خاصية children.
    • IconButton: مكون في Chakra UI مفيد عندما تريد استخدام أيقونة كزر. يتم تمرير الأيقونة المراد عرضها داخل خاصية icon.

    إليك كيف سيبدو حقل الإدخال:

    حقل إدخال البحث مع زر أيقونة البحث في Chakra UI

  4. إدارة حالة الإدخال ومعالجة الإرسال:

    هذا النموذج لا يفعل شيئًا بعد. دعنا نغير ذلك.
    عرّف حالة جديدة باسم query لتخزين مدخلات المستخدم:

    export default function Home({ data }) {
          const [photos, setPhotos] = useState(data);
          const [query, setQuery] = useState("");
          // ...
        }
    

    عدّل مكون Input لإنشاء ربط ثنائي الاتجاه (two-way bind) بين حقل الإدخال وحالة query باستخدام خاصية value وحدث onChange:

    <Input placeholder="Search for Apple" variant="ghost" value={query} onChange={(e) => setQuery(e.target.value)} />
    

    الآن، أنشئ دالة باسم handleSubmit() لمعالجة حدث النقر على أيقونة البحث. في الوقت الحالي، سنقوم فقط بطباعة الاستعلام إلى وحدة التحكم ومسح الحقل بعد ذلك:

    export default function Home({ data }) {
          const [photos, setPhotos] = useState(data);
          const [query, setQuery] = useState("");
    
          const handleSubmit = async (e) => {
            await e.preventDefault();
            await console.log(query);
            await setQuery("");
          };
          // ...
        }
    

    أضف هذه الدالة إلى حدث onClick لـ IconButton:

    <InputRightElement
          children={
            <IconButton
              aria-label="Search"
              icon={<SearchIcon />}
              onClick={handleSubmit}
              bg="pink.400"
              color="white"
            />
          }
        /
    

    توجه إلى http://localhost:3000/ واكتب شيئًا في حقل الإدخال وانقر على زر البحث.

    طباعة استعلام البحث إلى وحدة تحكم المتصفح

    ولكن هذا النموذج لا يزال ينقصه شيء: إذا حاولت البحث عن شيء بالضغط على Enter بدلاً من زر البحث، فستتم إعادة تحميل الصفحة، ولن يتم تسجيل الاستعلام. لإصلاح ذلك، قم بتغليف InputGroup بعنصر <form> ومرر دالة handleSubmit إلى حدث onSubmit كالتالي:

    <form onSubmit={handleSubmit}>
          <InputGroup pb="1rem">
            <Input
              placeholder="Search for Apple"
              variant="ghost"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            /
            <InputRightElement
              children={
                <IconButton
                  aria-label="Search"
                  icon={<SearchIcon />}
                  onClick={handleSubmit}
                  bg="pink.400"
                  color="white"
                /
              }
            /
          </InputGroup>
    </form>
    

    الآن، ستلاحظ أن الضغط على Enter سيعمل بشكل صحيح.

  5. تحديث دالة handleSubmit لجلب الصور:

    حدّث دالة handleSubmit على النحو التالي لجلب الصور بناءً على استعلام المستخدم:

    const handleSubmit = async (e) => {
          await e.preventDefault();
          const res = await getQueryPhotos(query);
          await setPhotos(res);
          await setQuery("");
        };
    

    تقوم الدالة أعلاه بتمرير متغير query إلى دالة getQueryPhotos()، وتقوم البيانات التي يتم إرجاعها من الدالة بتجاوز القيمة السابقة في متغير photos باستخدام setPhotos(res). وبهذا تكون قد انتهيت! يمكنك الآن البحث عن الصور في تطبيقك.

    عرض توضيحي لوظيفة البحث عن الصور في معرض Next.js

  6. معالجة استعلام البحث الفارغ باستخدام Toast:

    لا يزال هناك شيء مفقود. ماذا لو حاول المستخدم البحث بدون أي استعلام، أي بسلسلة نصية فارغة (empty string)؟ سيظل الكود الحالي يحاول إجراء طلب باستخدام ""، وسنواجه الخطأ التالي:

    خطأ يظهر عند محاولة البحث باستعلام فارغ

    لمعالجة هذه المشكلة، سنستخدم مكون Toast من Chakra UI لعرض رسالة خطأ للمستخدم.
    قم باستيراد useToast من Chakra UI:

    import {
          Box,
          Container,
          Text,
          Wrap,
          WrapItem,
          Input,
          IconButton,
          InputRightElement,
          InputGroup,
          useToast,
        } from "@chakra-ui/react";
    

    أضف الكود التالي مباشرة بعد تعريف الحالات (states) لتهيئة Toast:

    export default function Home({ data }) {
          const [photos, setPhotos] = useState(data);
          const [query, setQuery] = useState("");
          const toast = useToast();
          // ...
        }
    

    عدّل دالة handleSubmit() كالتالي:

    const handleSubmit = async (e) => {
          await e.preventDefault();
          if (query === "") {
            toast({
              title: "خطأ في البحث.",
              description: "الرجاء إدخال كلمة للبحث.",
              status: "error",
              duration: 9000,
              isClosable: true,
              position: "top",
            });
          } else {
            const res = await getQueryPhotos(query);
            await setPhotos(res);
            await setQuery("");
          }
        };
    

    في الكود أعلاه، نتحقق مما إذا كان query فارغًا أم لا باستخدام عبارة if/else بسيطة. وإذا كان فارغًا، فإننا نعرض رسالة خطأ باستخدام toast مع نص “الرجاء إدخال كلمة للبحث.”.

    جرب الضغط على Enter دون كتابة أي شيء في حقل الإدخال. سترى رسالة toast بهذا الشكل:

    رسالة خطأ Toast تظهر عند البحث باستعلام فارغ

إضافة مسارات ديناميكية للصور

سنقوم بإنشاء مسار ديناميكي (dynamic route) لكل صورة، مما يسمح للمستخدمين بالنقر على الصور للحصول على مزيد من المعلومات التفصيلية عنها. تتميز Next.js بميزة رائعة تتيح لك إنشاء مسارات ديناميكية عن طريق إضافة أقواس مربعة إلى اسم الصفحة (مثل [param])، حيث يمكن أن يكون param عبارة عن معرف (ID) أو جزء من URL. في حالتنا هذه، سيكون param هو id، نظرًا لأننا نحتاج إلى توفير معرف الصورة للحصول على صورة محددة من Pexels API.

  1. إنشاء ملف المسار الديناميكي:

    في المجلد الجذر لمشروعك، قم بتشغيل الأوامر التالية لإنشاء ملف [id].js داخل مجلد photos ضمن مجلد pages:

    mkdir pages/photos
    cd pages/photos
    touch [id].js
    
  2. استيراد مكون Link:

    قم باستيراد مكون Link من next/link في ملف index.js. يساعد Link في الانتقالات من جانب العميل (client-side transitions) بين المسارات، مما يوفر تجربة تصفح أسرع. يمكنك قراءة المزيد حول Link في توثيقات Next.js Link.

    import Link from "next/link";
    
  3. إضافة Link إلى كل صورة:

    أضف Link إلى كل صورة كالتالي:

    <Link href={`/photos/${pic.id}`}>
          <a>
            <Image src={pic.src.portrait} height={600} width={400} alt={pic.url} />
          </a>
    </Link>
    

    توجه إلى تطبيقك وحاول النقر على أي صورة. ستظهر لك رسالة خطأ لأننا أنشأنا ملف photos/[id].js ولكن لم نضف أي كود بداخله بعد. ولكن إذا لاحظت URL لهذه الصفحة، فسيكون شيئًا كهذا:

    http://localhost:3000/photos/2977079
    
  4. إنشاء دالة getPhotoById():

    سنقوم الآن بإنشاء دالة ثالثة باسم getPhotoById() في ملف lib/api.js للحصول على صورة محددة بناءً على معرفها (ID). أضف الكود التالي إلى ملف api.js:

    export const getPhotoById = async (id) => {
          const res = await fetch(`https://api.pexels.com/v1/photos/${id}`, {
            headers: {
              Authorization: API_KEY,
            },
          });
          const responseJson = await res.json();
          return responseJson;
        };
    

    يستخدم الكود أعلاه نقطة النهاية /photos للحصول على صورة واحدة من Pexels API. ستلاحظ أنه على عكس دالتي getCuratedPhotos وgetQueryPhotos، فإن دالة getPhotoById تُرجع الكائن responseJson بالكامل وليس فقط حقل responseJson.photos.

  5. تكوين صفحة الصورة الفردية (photos/[id].js):

    أضف الكود التالي إلى ملف photos/[id].js:

    import { getPhotoById } from "../../lib/api";
    import {
          Box,
          Divider,
          Center,
          Text,
          Flex,
          Spacer,
          Button,
        } from "@chakra-ui/react";
        import Image from "next/image";
        import Head from "next/head";
        import Link from "next/link";
        import { InfoIcon, AtSignIcon } from "@chakra-ui/icons";
    
        export default function Photos({ pic }) {
          return (
            <Box p="2rem" bg="gray.200" minH="100vh">
              <Head>
                <title>Image: {pic.id}</title>
                <link rel="icon" href="/favicon.ico" />
              </Head>
    
              <Flex px="1rem" justify="center" align="center">
                <Text
                  letterSpacing="wide"
                  textDecoration="underline"
                  as="h2"
                  fontWeight="semibold"
                  fontSize="xl"
                  as="a"
                  target="_blank"
                  href={pic.photographer_url}
                >
                  <AtSignIcon /> {pic.photographer}
                </Text>
                <Spacer />
                <Box as="a" target="_blank" href={pic.url}>
                  <InfoIcon focusable="true" boxSize="2rem" color="red.500" />{" "}
                </Box>{" "}
                <Spacer />
                <Link href={`/`}>
                  <Button
                    as="a"
                    borderRadius="full"
                    colorScheme="pink"
                    fontSize="lg"
                    size="lg"
                    cursor="pointer"
                  >
                    🏠 الرئيسية
                  </Button>
                </Link>
              </Flex>
    
              <Divider my="1rem" />
    
              <Center>
                <Box as="a" target="_blank" href={pic.url}>
                  <Image
                    src={pic.src.original}
                    width={pic.width / 4}
                    height={pic.height / 4}
                    quality={50}
                    priority
                    loading="eager"
                  /
                </Box>
              </Center>
            </Box>
          );
        }
    
        export async function getServerSideProps({ params }) {
          const pic = await getPhotoById(params.id);
          return {
            props: { pic },
          };
        }
    

    لقد أضفنا لون خلفية رمادي فاتح باستخدام خاصية bg ومكون Box. لتوفير الوقت، قمنا باستيراد جميع المكونات والأيقونات مسبقًا.

    أعد تشغيل خادم التطوير الخاص بك. قد تتساءل كيف تحصل دالة getServerSideProps() على معرف الصورة (id) من معامل params؟ نظرًا لأن هذه الصفحة تستخدم مسارًا ديناميكيًا، فإن الكائن params يحتوي على معاملات المسار. هنا، اسم الصفحة هو [id].js، لذا سيبدو الكائن params كالتالي: { id: ... }. يمكنك محاولة طباعة console.log(params) وسترى شيئًا كهذا:

    { id: '4956064' }
    

    قم بتمرير خاصية pic هذه إلى دالة مكون Photos كمعامل.

    إليك كيف ستبدو صفحتك الآن:

    صفحة عرض الصورة الفردية في معرض Next.js

    تحليل الكود خطوة بخطوة:

    • تعديل عنوان الصفحة:

      نقوم أولاً بتعديل عنوان الصفحة، عن طريق تمرير معرف الصورة بعد نص “Image”:

      <Head>
                    <title>Image: {pic.id}</title>
                    <link rel="icon" href="/favicon.ico" />
                  </Head>
      
    • إنشاء شريط التنقل (Navbar):

      ثم نقوم بإنشاء شريط تنقل باستخدام مكون Flex:

      <Flex px="1rem" justify="center" align="center">
                    // ...
                  </Flex>
      

      هنا، px هو اختصار لخاصيتي padding-left وpadding-right، وjustify وalign هما اختصار لـ justify-content وalign-items على التوالي.

    • إضافة رابط المصور:

      بعد ذلك، نضيف رابطًا للمصور باستخدام مكون Text وأيقونة AtSignIcon. يمكنك أيضًا استخدام علامة @ بدلاً من AtSignIcon.

      <Text
                    letterSpacing="wide"
                    textDecoration="underline"
                    as="h2"
                    fontWeight="semibold"
                    fontSize="xl"
                    as="a"
                    target="_blank"
                    href={pic.photographer_url}
                  >
                    <AtSignIcon /> {pic.photographer}
                  </Text>
      

      خاصية as هي ميزة في Chakra UI تسمح لك بتمرير وسم HTML أو مكون ليتم عرضه. هنا، نستخدمها مع وسم <a> بحيث يتم عرض مكون Text كـ وسم <a> على الصفحة. تضمن target="_blank" أن يفتح الرابط في نافذة أو علامة تبويب جديدة.

      رابط المصور في صفحة تفاصيل الصورة

    • مكون Spacer:

      ثم نضيف مكون Spacer الذي، عند استخدامه مع Flex، يوزع المساحة الفارغة بين العناصر الفرعية لـ Flex. يمكنك قراءة المزيد حوله في توثيقات Chakra UI Spacer.

      توضيح لمكون Spacer في Chakra UI

    • أيقونة المعلومات:

      بعد ذلك، نضيف أيقونة معلومات ترتبط بالصورة الأصلية على Pexels.

      <Box as="a" target="_blank" href={pic.url}>
                    <InfoIcon focusable="true" boxSize="2rem" color="red.500" />
                  </Box>{" "}
                  <Spacer />
      

      أيقونة المعلومات التي تربط بالصورة الأصلية على Pexels

    • زر العودة للصفحة الرئيسية:

      ثم نضيف زر “الرئيسية” في شريط التنقل لإعادة المستخدم إلى الصفحة الرئيسية للتطبيق باستخدام مكون Link من next/link.

      <Link href={`/`}>
                    <Button
                      as="a"
                      borderRadius="full"
                      colorScheme="pink"
                      fontSize="lg"
                      size="lg"
                      cursor="pointer"
                    >
                      🏠 الرئيسية
                    </Button>
                  </Link>
      

      زر الرئيسية في شريط التنقل

    • مكون Divider:

      ثم نستخدم مكون Divider لتقسيم شريط التنقل والصورة.

      <Divider my="1rem" />
      

      هنا، my هو اختصار لخاصيتي margin-top وmargin-bottom.

    • عرض الصورة المركزية:

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

      <Center>
                    <Box as="a" target="_blank" href={pic.url}>
                      <Image
                        src={pic.src.original}
                        width={pic.width / 4}
                        height={pic.height / 4}
                        quality={50}
                        priority
                        loading="eager"
                      /
                  </Box>
                </Center>
      

      في الكود أعلاه، نستخدم مكون Box لإضافة رابط للصورة الأصلية على Pexels باستخدام خاصية as. ستلاحظ أيضًا أننا مررنا بعض الخصائص الإضافية إلى مكون Image:

      • src: نمرر الصورة الأصلية (original) هذه المرة. نقوم بتغيير حجم الصورة عن طريق قسمة العرض والارتفاع الأصليين على 4.
      • priority: بتمرير هذه الخاصية، تُعتبر الصورة ذات أولوية عالية ويتم تحميلها مسبقًا (preloaded).
      • quality={50}: بشكل افتراضي، يقلل مكون Image جودة الصور المحسنة إلى 75%، ولكن نظرًا لأن الصورة لا تزال كبيرة جدًا، فإننا نقلل جودتها إلى 50% لزيادة التحسين.
      • loading="eager": بشكل افتراضي، يكون سلوك التحميل كسولًا (lazy) في مكون Image، ولكن هنا نريد عرض الصورة فورًا، وبالتالي نمرر loading="eager".

      إليك الكود أعلاه وهو يعمل:

      معرض صور Next.js مع مسارات ديناميكية وتفاصيل الصور

      لقد نجحت! 🎉 تهانينا 👏 على بناء مشروع معرض صور Next.js هذا.

الخلاصة التقنية

في هذا الدرس الشامل، استعرضنا خطوة بخطوة كيفية بناء معرض صور متكامل وديناميكي باستخدام إطار العمل Next.js. لقد تعلمنا كيفية الاستفادة من Pexels API لجلب الصور، وتطبيق مكتبة Chakra UI لتصميم واجهة مستخدم عصرية ومتجاوبة. الأهم من ذلك، تعمقنا في أهمية تحسين الصور باستخدام مكون Next.js Image Component لضمان أفضل أداء وتحميل سريع للصفحات، وهو عامل حاسم لتجربة المستخدم ونجاح SEO. كما اكتشفنا قوة المسارات الديناميكية في Next.js لإنشاء صفحات تفصيلية لكل صورة.

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

واجهات برمجة تطبيقات أخرى يمكنك استكشافها:

مصادر إضافية مفيدة:

هل ترغب في جزء ثانٍ لهذا الدرس، حيث نضيف تأثيرات حركية للصور باستخدام Framer Motion؟ أخبرني على Twitter!
ما هي المشاريع أو الدروس التعليمية الأخرى التي ترغب في رؤيتها؟ تواصل معي على Twitter، وسأقوم بتغطيتها في مقالي القادم!
إذا ألهمك هذا المقال لإضافة ميزات بنفسك، يرجى المشاركة ووضع علامة لي – أحب أن أسمع عنها 🙂

أشوتوش ك. سينغ
اقرأ المزيد من المنشورات. إذا كان هذا المقال مفيدًا، شاركه.
تعلم البرمجة مجانًا. ساعد منهج freeCodeCamp مفتوح المصدر أكثر من 40,000 شخص في الحصول على وظائف كمطورين. ابدأ الآن.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *