بناء تطبيق متكامل باستخدام PocketBase و Next.js في 2026

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

PocketBase هو خادم خلفي مفتوح المصدر مكتوب بلغة Go يأتي في ملف تنفيذي واحد. يوفر قاعدة بيانات SQLite ومصادقة مدمجة وتخزين ملفات واشتراكات في الوقت الفعلي ولوحة تحكم للإدارة — كل ذلك بدون إعدادات معقدة. عند دمجه مع Next.js، يشكّل حزمة تقنية حديثة وخفيفة ومثالية للمشاريع الشخصية والنماذج الأولية والتطبيقات متوسطة الحجم.

في هذا الدليل، ستبني تطبيقًا كاملاً لإدارة المهام مع مصادقة المستخدم وعمليات CRUD في الوقت الفعلي والنشر الجاهز للإنتاج.

المتطلبات الأساسية

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

  • Node.js 18+ مثبّت على جهازك
  • npm أو pnpm كمدير حزم
  • معرفة أساسية بـ React و TypeScript
  • محرر أكواد (يُنصح بـ VS Code)
  • طرفية (bash أو zsh أو PowerShell)

ما ستبنيه

تطبيق لإدارة المهام يتضمن الميزات التالية:

  • تسجيل الدخول وإنشاء الحساب
  • إنشاء المهام وقراءتها وتحديثها وحذفها
  • تحديثات في الوقت الفعلي عبر اشتراكات PocketBase
  • واجهة متجاوبة مع Tailwind CSS
  • جاهز للنشر في بيئة الإنتاج

الخطوة 1: تثبيت PocketBase

يتم توزيع PocketBase كملف تنفيذي واحد. قم بتنزيله من الموقع الرسمي.

# إنشاء مجلد للخادم الخلفي
mkdir pocketbase-backend && cd pocketbase-backend
 
# تنزيل PocketBase (Linux/macOS)
# زُر https://pocketbase.io/docs/ للحصول على أحدث إصدار
wget https://github.com/pocketbase/pocketbase/releases/download/v0.25.0/pocketbase_0.25.0_linux_amd64.zip
 
# أو على macOS باستخدام Homebrew
brew install pocketbase
 
# فك الضغط
unzip pocketbase_0.25.0_linux_amd64.zip

شغّل PocketBase:

./pocketbase serve

ستظهر في الطرفية:

> Server started at: http://127.0.0.1:8090
> - REST API: http://127.0.0.1:8090/api/
> - Admin UI: http://127.0.0.1:8090/_/

افتح http://127.0.0.1:8090/_/ في المتصفح للوصول إلى لوحة الإدارة. عند أول دخول، أنشئ حساب مسؤول.

الخطوة 2: إعداد مجموعات PocketBase

في لوحة الإدارة، أنشئ مجموعة tasks بالحقول التالية:

الحقلالنوعالخيارات
titleTextمطلوب، 200 حرف كحد أقصى
descriptionTextاختياري
completedBoolالقيمة الافتراضية: false
userRelationالمجموعة: users، مطلوب

إعداد قواعد الوصول

في تبويب API Rules لمجموعة tasks:

  • List/Search: @request.auth.id != "" && user = @request.auth.id
  • View: @request.auth.id != "" && user = @request.auth.id
  • Create: @request.auth.id != ""
  • Update: @request.auth.id != "" && user = @request.auth.id
  • Delete: @request.auth.id != "" && user = @request.auth.id

تضمن هذه القواعد أن كل مستخدم يمكنه فقط عرض وتعديل مهامه الخاصة.

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

افتح طرفية جديدة وأنشئ مشروع الواجهة الأمامية:

npx create-next-app@latest pocketbase-todo --typescript --tailwind --app --src-dir --use-npm
cd pocketbase-todo

ثبّت حزمة PocketBase SDK:

npm install pocketbase

الخطوة 4: إعداد عميل PocketBase

أنشئ ملف إعداد عميل PocketBase:

// src/lib/pocketbase.ts
import PocketBase from "pocketbase";
 
const pb = new PocketBase("http://127.0.0.1:8090");
 
// تعطيل الإلغاء التلقائي لتجنب التعارض مع React
pb.autoCancellation(false);
 
export default pb;

أنشئ أنواع TypeScript لبياناتك:

// src/types/index.ts
export interface Task {
  id: string;
  title: string;
  description: string;
  completed: boolean;
  user: string;
  created: string;
  updated: string;
}
 
export interface User {
  id: string;
  email: string;
  name: string;
  avatar: string;
}

الخطوة 5: إنشاء سياق المصادقة

أنشئ مزوّد React لإدارة حالة المصادقة بشكل عام:

// src/contexts/AuthContext.tsx
"use client";
 
import {
  createContext,
  useContext,
  useEffect,
  useState,
  useCallback,
  type ReactNode,
} from "react";
import pb from "@/lib/pocketbase";
import type { User } from "@/types";
 
interface AuthContextType {
  user: User | null;
  isLoading: boolean;
  login: (email: string, password: string) => Promise<void>;
  register: (email: string, password: string, name: string) => Promise<void>;
  logout: () => void;
}
 
const AuthContext = createContext<AuthContextType | undefined>(undefined);
 
export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
 
  useEffect(() => {
    // التحقق مما إذا كان المستخدم مسجل الدخول بالفعل
    if (pb.authStore.isValid) {
      const model = pb.authStore.model;
      if (model) {
        setUser({
          id: model.id,
          email: model.email,
          name: model.name || "",
          avatar: model.avatar || "",
        });
      }
    }
    setIsLoading(false);
 
    // الاستماع لتغييرات المصادقة
    const unsubscribe = pb.authStore.onChange((_token, model) => {
      if (model) {
        setUser({
          id: model.id,
          email: model.email,
          name: model.name || "",
          avatar: model.avatar || "",
        });
      } else {
        setUser(null);
      }
    });
 
    return () => unsubscribe();
  }, []);
 
  const login = useCallback(async (email: string, password: string) => {
    await pb.collection("users").authWithPassword(email, password);
  }, []);
 
  const register = useCallback(
    async (email: string, password: string, name: string) => {
      await pb.collection("users").create({
        email,
        password,
        passwordConfirm: password,
        name,
      });
      // تسجيل الدخول تلقائيًا بعد إنشاء الحساب
      await pb.collection("users").authWithPassword(email, password);
    },
    []
  );
 
  const logout = useCallback(() => {
    pb.authStore.clear();
    setUser(null);
  }, []);
 
  return (
    <AuthContext.Provider value={{ user, isLoading, login, register, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
 
export function useAuth() {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
}

الخطوة 6: إنشاء Hook لإدارة المهام

يغلّف هذا الـ Hook المخصص جميع عمليات CRUD والاشتراكات في الوقت الفعلي:

// src/hooks/useTasks.ts
"use client";
 
import { useState, useEffect, useCallback } from "react";
import pb from "@/lib/pocketbase";
import type { Task } from "@/types";
import { useAuth } from "@/contexts/AuthContext";
 
export function useTasks() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const { user } = useAuth();
 
  // جلب المهام
  const fetchTasks = useCallback(async () => {
    if (!user) return;
    try {
      setIsLoading(true);
      const records = await pb.collection("tasks").getFullList<Task>({
        sort: "-created",
        filter: `user = "${user.id}"`,
      });
      setTasks(records);
    } catch (error) {
      console.error("خطأ في تحميل المهام:", error);
    } finally {
      setIsLoading(false);
    }
  }, [user]);
 
  // الاشتراك في الوقت الفعلي
  useEffect(() => {
    if (!user) return;
 
    fetchTasks();
 
    // الاشتراك في تغييرات المجموعة
    pb.collection("tasks").subscribe<Task>("*", (event) => {
      switch (event.action) {
        case "create":
          setTasks((prev) => [event.record, ...prev]);
          break;
        case "update":
          setTasks((prev) =>
            prev.map((task) =>
              task.id === event.record.id ? event.record : task
            )
          );
          break;
        case "delete":
          setTasks((prev) =>
            prev.filter((task) => task.id !== event.record.id)
          );
          break;
      }
    });
 
    return () => {
      pb.collection("tasks").unsubscribe("*");
    };
  }, [user, fetchTasks]);
 
  // إنشاء مهمة
  const createTask = useCallback(
    async (title: string, description: string = "") => {
      if (!user) return;
      await pb.collection("tasks").create({
        title,
        description,
        completed: false,
        user: user.id,
      });
    },
    [user]
  );
 
  // تبديل حالة المهمة
  const toggleTask = useCallback(async (task: Task) => {
    await pb.collection("tasks").update(task.id, {
      completed: !task.completed,
    });
  }, []);
 
  // حذف مهمة
  const deleteTask = useCallback(async (taskId: string) => {
    await pb.collection("tasks").delete(taskId);
  }, []);
 
  // تحديث مهمة
  const updateTask = useCallback(
    async (taskId: string, data: Partial<Task>) => {
      await pb.collection("tasks").update(taskId, data);
    },
    []
  );
 
  return {
    tasks,
    isLoading,
    createTask,
    toggleTask,
    deleteTask,
    updateTask,
    refetch: fetchTasks,
  };
}

الخطوة 7: بناء مكونات واجهة المستخدم

نموذج تسجيل الدخول والتسجيل

// src/components/AuthForm.tsx
"use client";
 
import { useState, type FormEvent } from "react";
import { useAuth } from "@/contexts/AuthContext";
 
export default function AuthForm() {
  const [isLogin, setIsLogin] = useState(true);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");
  const [error, setError] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);
  const { login, register } = useAuth();
 
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError("");
    setIsSubmitting(true);
 
    try {
      if (isLogin) {
        await login(email, password);
      } else {
        await register(email, password, name);
      }
    } catch (err: unknown) {
      const message =
        err instanceof Error ? err.message : "حدث خطأ ما";
      setError(message);
    } finally {
      setIsSubmitting(false);
    }
  };
 
  return (
    <div className="mx-auto max-w-md rounded-xl bg-white p-8 shadow-lg">
      <h2 className="mb-6 text-center text-2xl font-bold text-gray-800">
        {isLogin ? "تسجيل الدخول" : "إنشاء حساب"}
      </h2>
 
      {error && (
        <div className="mb-4 rounded-lg bg-red-50 p-3 text-sm text-red-600">
          {error}
        </div>
      )}
 
      <form onSubmit={handleSubmit} className="space-y-4">
        {!isLogin && (
          <input
            type="text"
            placeholder="اسمك"
            value={name}
            onChange={(e) => setName(e.target.value)}
            className="w-full rounded-lg border px-4 py-3 focus:border-blue-500 focus:outline-none"
            required
          />
        )}
        <input
          type="email"
          placeholder="البريد الإلكتروني"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className="w-full rounded-lg border px-4 py-3 focus:border-blue-500 focus:outline-none"
          required
        />
        <input
          type="password"
          placeholder="كلمة المرور"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          className="w-full rounded-lg border px-4 py-3 focus:border-blue-500 focus:outline-none"
          required
          minLength={8}
        />
        <button
          type="submit"
          disabled={isSubmitting}
          className="w-full rounded-lg bg-blue-600 py-3 font-medium text-white transition hover:bg-blue-700 disabled:opacity-50"
        >
          {isSubmitting
            ? "جارٍ التحميل..."
            : isLogin
              ? "تسجيل الدخول"
              : "إنشاء حساب"}
        </button>
      </form>
 
      <p className="mt-4 text-center text-sm text-gray-600">
        {isLogin ? "ليس لديك حساب؟" : "لديك حساب بالفعل؟"}
        <button
          onClick={() => setIsLogin(!isLogin)}
          className="mr-1 font-medium text-blue-600 hover:underline"
        >
          {isLogin ? "سجّل الآن" : "سجّل دخولك"}
        </button>
      </p>
    </div>
  );
}

مكون قائمة المهام

// src/components/TaskList.tsx
"use client";
 
import { useState, type FormEvent } from "react";
import { useTasks } from "@/hooks/useTasks";
import { useAuth } from "@/contexts/AuthContext";
import type { Task } from "@/types";
 
function TaskItem({
  task,
  onToggle,
  onDelete,
}: {
  task: Task;
  onToggle: (task: Task) => void;
  onDelete: (id: string) => void;
}) {
  return (
    <div className="group flex items-center gap-3 rounded-lg border bg-white p-4 transition hover:shadow-md">
      <button
        onClick={() => onToggle(task)}
        className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full border-2 transition ${
          task.completed
            ? "border-green-500 bg-green-500 text-white"
            : "border-gray-300 hover:border-blue-400"
        }`}
      >
        {task.completed && (
          <svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
          </svg>
        )}
      </button>
 
      <div className="flex-1">
        <h3
          className={`font-medium ${
            task.completed ? "text-gray-400 line-through" : "text-gray-800"
          }`}
        >
          {task.title}
        </h3>
        {task.description && (
          <p className="mt-1 text-sm text-gray-500">{task.description}</p>
        )}
      </div>
 
      <button
        onClick={() => onDelete(task.id)}
        className="rounded-lg p-2 text-gray-400 opacity-0 transition hover:bg-red-50 hover:text-red-500 group-hover:opacity-100"
      >
        <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
        </svg>
      </button>
    </div>
  );
}
 
export default function TaskList() {
  const [newTitle, setNewTitle] = useState("");
  const [newDescription, setNewDescription] = useState("");
  const { tasks, isLoading, createTask, toggleTask, deleteTask } = useTasks();
  const { user, logout } = useAuth();
 
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    if (!newTitle.trim()) return;
    await createTask(newTitle.trim(), newDescription.trim());
    setNewTitle("");
    setNewDescription("");
  };
 
  const completedCount = tasks.filter((t) => t.completed).length;
 
  return (
    <div className="mx-auto max-w-2xl">
      {/* الرأس */}
      <div className="mb-8 flex items-center justify-between">
        <div>
          <h1 className="text-3xl font-bold text-gray-800">مهامي</h1>
          <p className="mt-1 text-gray-500">
            مرحبًا، {user?.name || user?.email}
          </p>
        </div>
        <button
          onClick={logout}
          className="rounded-lg px-4 py-2 text-sm text-gray-600 transition hover:bg-gray-100"
        >
          تسجيل الخروج
        </button>
      </div>
 
      {/* نموذج الإضافة */}
      <form onSubmit={handleSubmit} className="mb-6 space-y-3">
        <input
          type="text"
          placeholder="إضافة مهمة جديدة..."
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
          className="w-full rounded-xl border-2 border-gray-200 px-4 py-3 text-lg focus:border-blue-500 focus:outline-none"
        />
        <div className="flex gap-3">
          <input
            type="text"
            placeholder="الوصف (اختياري)"
            value={newDescription}
            onChange={(e) => setNewDescription(e.target.value)}
            className="flex-1 rounded-lg border px-4 py-2 focus:border-blue-500 focus:outline-none"
          />
          <button
            type="submit"
            disabled={!newTitle.trim()}
            className="rounded-lg bg-blue-600 px-6 py-2 font-medium text-white transition hover:bg-blue-700 disabled:opacity-50"
          >
            إضافة
          </button>
        </div>
      </form>
 
      {/* الإحصائيات */}
      <div className="mb-4 flex gap-4 text-sm text-gray-500">
        <span>{tasks.length} مهمة إجمالاً</span>
        <span>{completedCount} مكتملة</span>
        <span>{tasks.length - completedCount} قيد التنفيذ</span>
      </div>
 
      {/* قائمة المهام */}
      {isLoading ? (
        <div className="py-12 text-center text-gray-400">
          جارٍ تحميل المهام...
        </div>
      ) : tasks.length === 0 ? (
        <div className="py-12 text-center text-gray-400">
          <p className="text-lg">لا توجد مهام حتى الآن</p>
          <p className="mt-2 text-sm">أنشئ مهمتك الأولى أعلاه</p>
        </div>
      ) : (
        <div className="space-y-2">
          {tasks.map((task) => (
            <TaskItem
              key={task.id}
              task={task}
              onToggle={toggleTask}
              onDelete={deleteTask}
            />
          ))}
        </div>
      )}
    </div>
  );
}

الخطوة 8: تجميع الصفحة الرئيسية

ادمج مزوّد المصادقة في التخطيط:

// src/app/layout.tsx
import type { Metadata } from "next";
import { AuthProvider } from "@/contexts/AuthContext";
import "./globals.css";
 
export const metadata: Metadata = {
  title: "تطبيق المهام - PocketBase + Next.js",
  description: "تطبيق إدارة المهام باستخدام PocketBase و Next.js",
};
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  );
}

أنشئ الصفحة الرئيسية التي تعرض إما نموذج المصادقة أو قائمة المهام:

// src/app/page.tsx
"use client";
 
import AuthForm from "@/components/AuthForm";
import TaskList from "@/components/TaskList";
import { useAuth } from "@/contexts/AuthContext";
 
export default function Home() {
  const { user, isLoading } = useAuth();
 
  if (isLoading) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-gray-50">
        <div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
      </div>
    );
  }
 
  return (
    <main className="min-h-screen bg-gray-50 px-4 py-12">
      {user ? <TaskList /> : <AuthForm />}
    </main>
  );
}

الخطوة 9: متغيرات البيئة

أنشئ ملف .env.local لإعداد رابط PocketBase:

NEXT_PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090

حدّث عميل PocketBase لاستخدام هذا المتغير:

// src/lib/pocketbase.ts
import PocketBase from "pocketbase";
 
const pb = new PocketBase(
  process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090"
);
 
pb.autoCancellation(false);
 
export default pb;

الخطوة 10: تشغيل التطبيق

افتح نافذتي طرفية:

# الطرفية 1: PocketBase
cd pocketbase-backend
./pocketbase serve
 
# الطرفية 2: Next.js
cd pocketbase-todo
npm run dev

افتح http://localhost:3000 في متصفحك. يجب أن ترى نموذج تسجيل الدخول. أنشئ حسابًا ثم ابدأ بإضافة المهام.

ميزات متقدمة

التصفية والبحث

أضف نظام تصفية إلى قائمة المهام:

// في useTasks.ts، أضف دالة البحث
const searchTasks = useCallback(
  async (query: string) => {
    if (!user) return;
    const records = await pb.collection("tasks").getFullList<Task>({
      sort: "-created",
      filter: `user = "${user.id}" && title ~ "${query}"`,
    });
    setTasks(records);
  },
  [user]
);

التصفّح بالصفحات

للتطبيقات التي تحتوي على بيانات كثيرة، استخدم التصفّح بالصفحات:

const fetchTasksPaginated = useCallback(
  async (page: number = 1, perPage: number = 20) => {
    if (!user) return;
    const result = await pb.collection("tasks").getList<Task>(page, perPage, {
      sort: "-created",
      filter: `user = "${user.id}"`,
    });
    return {
      items: result.items,
      totalPages: result.totalPages,
      totalItems: result.totalItems,
    };
  },
  [user]
);

رفع الملفات

يدير PocketBase الملفات بشكل أصلي. إليك كيفية إضافة مرفقات للمهام:

const createTaskWithFile = useCallback(
  async (title: string, file: File) => {
    if (!user) return;
    const formData = new FormData();
    formData.append("title", title);
    formData.append("user", user.id);
    formData.append("completed", "false");
    formData.append("attachment", file);
 
    await pb.collection("tasks").create(formData);
  },
  [user]
);

النشر في بيئة الإنتاج

نشر PocketBase

يمكن نشر PocketBase على أي خادم Linux:

# على الخادم
mkdir -p /opt/pocketbase
cd /opt/pocketbase
 
# تنزيل واستخراج PocketBase
wget https://github.com/pocketbase/pocketbase/releases/download/v0.25.0/pocketbase_0.25.0_linux_amd64.zip
unzip pocketbase_0.25.0_linux_amd64.zip
 
# إنشاء خدمة systemd
sudo tee /etc/systemd/system/pocketbase.service > /dev/null << 'EOF'
[Unit]
Description=PocketBase
After=network.target
 
[Service]
Type=simple
User=root
ExecStart=/opt/pocketbase/pocketbase serve --http="0.0.0.0:8090"
Restart=on-failure
RestartSec=5s
 
[Install]
WantedBy=multi-user.target
EOF
 
# تفعيل وتشغيل الخدمة
sudo systemctl enable pocketbase
sudo systemctl start pocketbase

نشر Next.js

انشر الواجهة الأمامية على Vercel أو Netlify أو خادمك الخاص:

# تحديث رابط PocketBase للإنتاج
# .env.production
NEXT_PUBLIC_POCKETBASE_URL=https://api.your-domain.com
 
# بناء الإنتاج
npm run build
npm start

إعداد Nginx (بروكسي عكسي)

server {
    listen 80;
    server_name api.your-domain.com;
 
    location / {
        proxy_pass http://127.0.0.1:8090;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
 
        # دعم WebSocket للوقت الفعلي
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

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

أخطاء CORS

إذا واجهت أخطاء CORS، شغّل PocketBase مع خيار origins:

./pocketbase serve --origins="http://localhost:3000,https://your-domain.com"

أخطاء اتصال WebSocket

تأكد من أن البروكسي العكسي مُعدّ لدعم WebSockets (راجع إعداد Nginx أعلاه مع ترويسات Upgrade و Connection).

البيانات لا تُحدَّث في الوقت الفعلي

تحقق من:

  1. أن عميل PocketBase مُعدّ بـ autoCancellation(false)
  2. أن الاشتراك (subscribe) مُهيّأ بشكل صحيح
  3. أن التنظيف (unsubscribe) يتم في return الخاص بـ useEffect

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

الآن بعد أن يعمل تطبيقك، إليك بعض الأفكار للمضي قدمًا:

  • إضافة تصنيفات: أنشئ مجموعة "categories" واربطها بالمهام
  • تنفيذ السحب والإفلات: استخدم @dnd-kit/core لإعادة ترتيب المهام
  • إضافة إشعارات: أرسل تذكيرات بالبريد الإلكتروني عبر hooks في PocketBase
  • وضع بدون اتصال: استخدم service worker للسماح بالاستخدام بدون إنترنت
  • اختبارات شاملة: أضف اختبارات Playwright للتحقق من تدفقات المستخدم

الخلاصة

لقد بنيت تطبيقًا متكاملاً باستخدام PocketBase و Next.js. يقدم PocketBase بديلاً خفيفًا وقويًا للخوادم الخلفية التقليدية، مع ميزات مثل المصادقة والتحديثات الفورية وتخزين الملفات — كل ذلك في ملف تنفيذي واحد.

مزيج PocketBase + Next.js مناسب بشكل خاص لـ:

  • النماذج الأولية السريعة والـ MVP
  • التطبيقات الشخصية والمشاريع الجانبية
  • التطبيقات الصغيرة والمتوسطة في بيئة الإنتاج
  • المطورين الذين يرغبون في التحكم الكامل في حزمتهم التقنية

الكود المصدري الكامل لهذا الدليل متاح كمرجع ويمكن تكييفه لمشاريعكم الخاصة.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على 7 أساسيات Laravel 11: الاستجابات.

ناقش مشروعك معنا

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

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

إضافة المصادقة لتطبيق Next.js 15 باستخدام Auth.js v5: البريد الإلكتروني وOAuth والتحكم بالأدوار

تعلم كيفية إضافة نظام مصادقة جاهز للإنتاج لتطبيق Next.js 15 باستخدام Auth.js v5. يغطي هذا الدليل الشامل تسجيل الدخول عبر Google OAuth وبيانات الاعتماد بالبريد الإلكتروني وكلمة المرور والمسارات المحمية والتحكم بالوصول حسب الأدوار.

30 د قراءة·

بناء روبوت دردشة ذكاء اصطناعي محلي باستخدام Ollama و Next.js: الدليل الشامل

ابنِ روبوت دردشة ذكاء اصطناعي خاص يعمل بالكامل على جهازك المحلي باستخدام Ollama و Next.js. يغطي هذا الدليل العملي التثبيت والبث المباشر واختيار النماذج وبناء واجهة دردشة جاهزة للإنتاج — كل ذلك دون إرسال بياناتك إلى السحابة.

25 د قراءة·