الكتابات/tutorial/2026/05
Tutorial11 مايو 2026·28 دقيقة

Storybook 8 مع Next.js 15: التطوير القائم على المكونات، الاختبار والتوثيق

تعلم كيفية دمج Storybook 8 في مشروع Next.js 15 لتطوير المكونات بشكل معزول، وكتابة اختبارات التفاعل، وفحص إمكانية الوصول، وتوليد التوثيق تلقائياً.

Storybook 8 هو الإصدار الأكثر أهمية الذي شهده المشروع منذ سنوات. يأتي مع بنية مُعاد بناؤها تعتمد على Vite أولاً، وقصة اختبار مُجددة كلياً مع اختبارات التفاعل المدمجة، ودعم من الدرجة الأولى لمكونات React Server. بالتزامن مع Next.js 15 وموجّه التطبيق (App Router)، تحصل على سير عمل قوي لتطوير مكونات واجهة المستخدم وتوثيقها واختبارها بشكل معزول تماماً — قبل أن تلمس أي صفحة.

في هذا الدرس، ستقوم بإعداد Storybook 8 داخل مشروع Next.js 15 جديد، وكتابة قصص باستخدام صيغة CSF3، واختبار تفاعلات المكونات، وإجراء فحوصات إمكانية الوصول، وتوليد توثيق احترافي تلقائياً. بحلول النهاية، سيكون سير عمل المكونات الخاص بك معزولاً تماماً وقابلاً للتكرار وجاهزاً للتكامل المستمر.

المتطلبات المسبقة

قبل البدء، تأكد من توفر ما يلي:

  • Node.js 20 أو أحدث مثبّت على جهازك
  • معرفة أساسية بـ React و TypeScript
  • مشروع Next.js جاهز (سنقوم بإنشاء واحد من الصفر)
  • npm أو pnpm كمدير للحزم

ما الذي ستبنيه

ستقوم ببناء مكوّن نظام تصميم بسيط — زر Button — ومكوّن أكثر تعقيداً هو ProductCard. على طول الطريق ستتعلم:

  • تثبيت وتهيئة Storybook 8 مع محوّل إطار Next.js
  • كتابة قصص CSF3 مع وسائط مكتوبة بالأنواع وعناصر تحكم مُولَّدة تلقائياً
  • إضافة مزخرفات لمحاكاة واجهات برمجة Next.js (Image وLink وRouter)
  • كتابة اختبارات التفاعل التي تعمل داخل Storybook وفي خط الأنابيب
  • تمكين إضافة إمكانية الوصول (a11y) لفحص الامتثال لمعايير WCAG
  • توليد توثيق تلقائي باستخدام autodocs
  • الدمج مع Chromatic للاختبار البصري التراجعي

الخطوة 1: إنشاء مشروع Next.js 15

ابدأ بإنشاء تطبيق Next.js 15 جديد مع TypeScript وTailwind CSS:

npx create-next-app@latest my-design-system \
  --typescript \
  --tailwind \
  --eslint \
  --app \
  --src-dir \
  --import-alias "@/*"
cd my-design-system

اقبل جميع الإعدادات الافتراضية. سيمنحك هذا مشروعاً نظيفاً يعتمد App Router تحت src/app/.

الخطوة 2: تهيئة Storybook 8

يأتي Storybook 8 مع أمر تهيئة ذكي يكتشف إطار العمل تلقائياً:

npx storybook@latest init

سيقوم الأمر بما يلي:

  1. اكتشاف Next.js وتثبيت @storybook/nextjs (محوّل الإطار الرسمي)
  2. إضافة التبعيات المطلوبة: storybook و@storybook/react و@storybook/addon-essentials
  3. توليد مجلد إعداد .storybook/
  4. إنشاء ملفات قصص نموذجية في src/stories/
  5. إضافة سكريبتَي storybook وbuild-storybook إلى package.json

بعد اكتمال التثبيت، قم بتشغيل خادم التطوير:

npm run storybook

افتح http://localhost:6006 في متصفحك. يجب أن ترى واجهة Storybook مع القصص النموذجية.

الخطوة 3: فهم ملفات الإعداد

يضع Storybook إعداداته في .storybook/. ملفان مهمان بشكل أساسي.

.storybook/main.ts — إعداد الإطار والإضافات:

import type { StorybookConfig } from "@storybook/nextjs";
 
const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
    "@storybook/addon-a11y",
  ],
  framework: {
    name: "@storybook/nextjs",
    options: {},
  },
  staticDirs: ["../public"],
};
 
export default config;

.storybook/preview.ts — المزخرفات والمعاملات العامة:

import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
 
const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};
 
export default preview;

إدخال staticDirs يخبر Storybook بتقديم الملفات من public/، حتى تتمكن next/image من تحليل الصور المحلية بشكل صحيح.

الخطوة 4: إنشاء مكوّن Button

أضف مكوّن Button قابل لإعادة الاستخدام في src/components/Button.tsx:

import { ButtonHTMLAttributes } from "react";
import { cva, type VariantProps } from "class-variance-authority";
 
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-blue-600 text-white hover:bg-blue-700",
        destructive: "bg-red-600 text-white hover:bg-red-700",
        outline: "border border-gray-300 bg-white hover:bg-gray-50 text-gray-900",
        ghost: "hover:bg-gray-100 text-gray-900",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-base",
        lg: "h-12 px-6 text-lg",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  }
);
 
export interface ButtonProps
  extends ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  label: string;
  loading?: boolean;
}
 
export function Button({ label, variant, size, loading, className, ...props }: ButtonProps) {
  return (
    <button
      className={buttonVariants({ variant, size, className })}
      disabled={loading || props.disabled}
      {...props}
    >
      {loading && (
        <svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
        </svg>
      )}
      {label}
    </button>
  );
}

ثبّت class-variance-authority إذا لم تكن قد فعلت ذلك بالفعل:

npm install class-variance-authority

الخطوة 5: كتابة أول قصة بصيغة CSF3

أنشئ src/components/Button.stories.tsx:

import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import { Button } from "./Button";
 
const meta = {
  title: "Design System/Button",
  component: Button,
  parameters: {
    layout: "centered",
  },
  tags: ["autodocs"],
  argTypes: {
    variant: {
      control: "select",
      options: ["default", "destructive", "outline", "ghost"],
    },
    size: {
      control: "select",
      options: ["sm", "md", "lg"],
    },
    loading: { control: "boolean" },
    disabled: { control: "boolean" },
  },
  args: {
    onClick: fn(),
    label: "انقر هنا",
  },
} satisfies Meta<typeof Button>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const Default: Story = {
  args: {
    variant: "default",
    size: "md",
  },
};
 
export const Destructive: Story = {
  args: {
    variant: "destructive",
    label: "حذف العنصر",
  },
};
 
export const Outline: Story = {
  args: {
    variant: "outline",
    label: "إلغاء",
  },
};
 
export const Loading: Story = {
  args: {
    loading: true,
    label: "جارٍ الحفظ...",
  },
};

نمط satisfies Meta<typeof Button> في CSF3 يمنحك استنتاج كامل للأنواع على args مع الحفاظ على قابلية توسعة كائن الميتا.

الخطوة 6: استخدام Args ولوحة Controls

في CSF3، args هي الخصائص الحية التي يتم تمريرها إلى مكوّنك. تقوم لوحة Controls (من addon-essentials) بإنشاء نموذج تلقائياً من أنواع TypeScript الخاصة بمكوّنك.

يمكنك تخصيص عناصر التحكم باستخدام argTypes لتجربة مستخدم أغنى:

argTypes: {
  variant: {
    description: "النمط المرئي للزر",
    control: { type: "select" },
    options: ["default", "destructive", "outline", "ghost"],
    table: {
      defaultValue: { summary: "default" },
    },
  },
}

تقوم إضافة Actions بتسجيل استدعاءات onClick في الوقت الفعلي. استخدام fn() من @storybook/test (وليس المساعد القديم action()) يعني أن اختبارات التفاعل الخاصة بك يمكنها التجسس على تلك الاستدعاءات أيضاً.

الخطوة 7: إضافة مزخرفات لمحاكاة واجهات برمجة Next.js

مكوّنات Next.js غالباً ما تستخدم next/link أو next/image أو useRouter. يتعامل محوّل @storybook/nextjs مع معظم هذا تلقائياً، لكنك قد تحتاج إلى مزخرفات عامة لمزودي الخدمات مثل الثيم أو الترجمة.

عدّل .storybook/preview.ts:

import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
 
const preview: Preview = {
  parameters: {
    nextjs: {
      appDirectory: true,
    },
  },
  decorators: [
    (Story) => (
      <div className="p-4">
        <Story />
      </div>
    ),
  ],
};
 
export default preview;

معامل nextjs.appDirectory: true يخبر المحوّل بأنك تستخدم App Router، مما يتيح دعم useRouter وusePathname وuseSearchParams في القصص.

لمحاكاة قيمة موجّه محددة لكل قصة:

export const ActiveLink: Story = {
  parameters: {
    nextjs: {
      router: {
        pathname: "/dashboard",
      },
    },
  },
};

الخطوة 8: كتابة اختبارات التفاعل

تعمل اختبارات التفاعل داخل واجهة Storybook وفي خط الأنابيب باستخدام @storybook/test-runner. تستخدم نفس واجهة برمجة @testing-library/user-event التي تعرفها بالفعل.

أضف دالة play إلى قصة Button:

import { expect, userEvent, within } from "@storybook/test";
 
export const ClickTracking: Story = {
  args: {
    label: "إرسال",
    onClick: fn(),
  },
  play: async ({ args, canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole("button", { name: /إرسال/i });
 
    await userEvent.click(button);
 
    expect(args.onClick).toHaveBeenCalledTimes(1);
  },
};

تشغيل جميع اختبارات التفاعل بدون واجهة رسومية:

npx concurrently -k -s first -n "SB,TEST" \
  "npm run storybook -- --quiet" \
  "npx wait-on tcp:6006 && npx test-storybook"

ثبّت مشغّل الاختبار:

npm install --save-dev @storybook/test-runner concurrently wait-on

الخطوة 9: اختبار إمكانية الوصول مع إضافة a11y

تقوم إضافة @storybook/addon-a11y بتشغيل axe-core تلقائياً على كل قصة.

ثبّتها:

npm install --save-dev @storybook/addon-a11y

أضفها إلى مصفوفة الإضافات في .storybook/main.ts (تم ذلك بالفعل في الخطوة 3). افتح أي قصة وانقر على تبويب Accessibility في لوحة الإضافات. ستظهر الانتهاكات باللون الأحمر والتحذيرات باللون الأصفر.

يمكنك تهيئة القواعد بشكل عام أو لكل قصة:

export const ContrastCheck: Story = {
  parameters: {
    a11y: {
      config: {
        rules: [
          {
            id: "color-contrast",
            enabled: true,
          },
        ],
      },
    },
  },
};

الخطوة 10: توليد التوثيق التلقائي مع autodocs

إضافة tags: ["autodocs"] إلى ميتا القصة يولّد صفحة توثيق كاملة تلقائياً. تتضمن صفحة التوثيق:

  • وصف المكوّن (من تعليقات JSDoc على المكوّن)
  • جدول الخصائص مع الأنواع والأوصاف والقيم الافتراضية
  • أمثلة تفاعلية حية لكل قصة مسماة

أضف JSDoc إلى خصائص مكوّنك للحصول على توثيق أغنى:

export interface ButtonProps {
  /** نص التسمية المعروض داخل الزر */
  label: string;
  /** يتحكم في النمط المرئي للزر */
  variant?: "default" | "destructive" | "outline" | "ghost";
  /** يتحكم في حجم الزر */
  size?: "sm" | "md" | "lg";
  /** يُظهر مؤشر دوران ويعطّل الزر عندما يكون true */
  loading?: boolean;
}

الخطوة 11: بناء Storybook للاستضافة الثابتة

أنشئ بناءً ثابتاً لاستضافته على أي شبكة توصيل محتوى:

npm run build-storybook

ينتقل الناتج إلى storybook-static/. يمكنك نشره على Vercel أو Netlify أو GitHub Pages. أضفه إلى خط أنابيب التكامل المستمر حتى تتضمن كل طلب سحب معاينة محدّثة للمكوّنات.

للتكامل مع GitHub Actions:

name: Storybook CI
on: [push, pull_request]
jobs:
  storybook:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build-storybook
      - run: npx concurrently -k -s first -n "SB,TEST"
          "npx http-server storybook-static --port 6006 --silent"
          "npx wait-on tcp:6006 && npx test-storybook --url http://localhost:6006"

الخطوة 12: الاختبار البصري التراجعي مع Chromatic

Chromatic هي خدمة سحابية بنتها فريق Storybook للاختبار البصري التراجعي. تلتقط لقطات بكسل لكل قصة وتنبّهك بالفروقات في واجهة المستخدم في طلبات السحب.

ثبّت أداة سطر الأوامر:

npm install --save-dev chromatic

شغّل أول بناء لإنشاء خط الأساس:

npx chromatic --project-token=YOUR_TOKEN

احصل على رمز مشروعك من chromatic.com. بعد أول تشغيل، سيقارن كل دفع في خط الأنابيب مع الخط الأساسي المقبول.

استكشاف الأخطاء وإصلاحها

فشل Storybook في البدء بعد ترقية Next.js شغّل npx storybook upgrade لتحديث جميع حزم Storybook إلى أحدث الإصدارات المتوافقة.

خطأ useRouter يرمي "invariant expected app router" أضف nextjs: { appDirectory: true } إلى معاملات .storybook/preview.ts كما هو موضح في الخطوة 7.

أنماط Tailwind لا تُحمَّل في Storybook تأكد من استيراد globals.css في أعلى .storybook/preview.ts. يجب أن يكون المسار نسبياً من مجلد .storybook/.

الصور المكسورة مع next/image أضف staticDirs: ["../public"] إلى .storybook/main.ts. الصور في public/ ستُقدَّم من خادم تطوير Storybook بنفس المسارات.

الخطوات التالية

  • استكشف @storybook/addon-viewport لاختبار التخطيطات المتجاوبة عبر أحجام الشاشات
  • استخدم MSW (Mock Service Worker) لمحاكاة استدعاءات API في القصص مع msw-storybook-addon
  • أضف Storybook Test كإضافة Vitest لتشغيل اختبارات التفاعل داخل مجموعة Vitest الحالية
  • اقرأ دليل ترحيل Storybook 8 إذا كنت تقوم بالترقية من الإصدار 7

الخلاصة

يجعل Storybook 8 التطوير القائم على المكونات جزءاً محورياً من سير عمل Next.js 15. لديك الآن إعداد يوفر:

  • تطوير المكوّنات بشكل معزول تماماً، بعيداً عن مخاوف مستوى الصفحة
  • توثيق كل مكوّن بأمثلة تفاعلية حية
  • اكتشاف انتهاكات إمكانية الوصول قبل أن تصل إلى المستخدمين
  • تشغيل اختبارات التفاعل في خط الأنابيب باستخدام نفس التأكيدات كاختبارات الوحدة
  • الكشف عن الانحدارات البصرية من خلال لقطات Chromatic

يتسع هذا سير العمل من زر Button واحد إلى نظام تصميم مؤسسي يضم مئات المكوّنات.