וורדפרס ללא ראש: שילוב REST API של WP עם React ו-Next.js

כשהעסק שלך זקוק לניהול תוכן נוח אך ערכות העיצוב הסטנדרטיות של WordPress אינן גמישות או מהירות מספיק, אנחנו מפרידים בין השרת לתצוגה, ומשאירים את WordPress כלוח ניהול נוח. הצוות שלנו בונה פתרונות headless על React, Next.js או Vue, ומחבר אותם דרך WP REST API. אנחנו מספקים פרויקטים מלאים—החל מהגדרת ה-API ועד השקת ממשק מהיר וסקיילבילי, עם תמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
וורדפרס ללא ראש: שילוב REST API של WP עם React ו-Next.js
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

וורדפרס ללא ראש (Headless WordPress): אינטגרציה של WP REST API עם React ו-Next.js

בעיה אופיינית: עסק צריך תוכן שניתן לניהול בוורדפרס, אבל תבניות מוכנות לא מספקות את הביצועים או ההתאמה הנדרשים. הפתרון הוא CMS ללא ראש (headless CMS), שבו וורדפרס נשארת כצד השרת (backend) וצד הלקוח (frontend) נבנה עם React, Vue, Next.js או Svelte. זה מבטל תבניות מונוליטיות, מאיץ את טעינת הדפים (LCP יורד ב-30–50%), ומפשט את התחזוקה. במהלך 5+ השנים האחרונות, השלמנו יותר מ-50 פרויקטים כאלה — מחנויות מקוונות ועד פורטלים ארגוניים. תקציב האינטגרציה דומה למשכורת חודשית של מפתח פרונטאנד, אבל הוא משתלם דרך הפחתת עלויות האחסון והאצת פריסת דפים חדשים.

אילו בעיות פותר וורדפרס ללא ראש?

  • הפרדת תוכן מהצגה: עורכים עובדים בניהול המוכר של וורדפרס, בעוד מפתחים משתמשים בטכנולוגיות מודרניות (React, Next.js, Vue). אין יותר צורך לכופף תבניות כדי להתאים לעיצוב — ה-API מספק נתונים נקיים.
  • שיפור ביצועים: דפים סטטיים (SSG) ו-ISR מפחיתים את TTFB ל-200 אלפיות השנייה ואת LCP לפחות מ-2 שניות. חיסכון במשאבי שרת מגיע ל-40% בזכות מטמון (caching).
  • API אחד לכל הלקוחות: וורדפרס אחת יכולה לשרת אפליקציית SPA, אפליקציה ניידת ובוט טלגרם — דרך REST או GraphQL.

אינטגרציה בסיסית: שליפת נתונים

ה-WP REST API זמין כברירת מחדל בכתובת /wp-json/wp/v2/. שליפת הפוסטים האחרונים:

// lib/wordpress.ts
const WP_API_URL = process.env.NEXT_PUBLIC_WP_URL + '/wp-json/wp/v2';

export interface WPPost {
  id: number;
  slug: string;
  title: { rendered: string };
  content: { rendered: string };
  excerpt: { rendered: string };
  date: string;
  featured_media: number;
  _embedded?: {
    'wp:featuredmedia'?: [{ source_url: string; alt_text: string }];
    'wp:term'?: Array<Array<{ id: number; name: string; slug: string }>>;
  };
}

export async function getPosts(params: {
  perPage?: number;
  page?: number;
  category?: number;
  search?: string;
} = {}): Promise<{ posts: WPPost[]; total: number; totalPages: number }> {
  const qs = new URLSearchParams({
    per_page: String(params.perPage ?? 12),
    page: String(params.page ?? 1),
    _embed: 'wp:featuredmedia,wp:term',
    ...(params.category && { categories: String(params.category) }),
    ...(params.search && { search: params.search }),
  });

  const res = await fetch(`${WP_API_URL}/posts?${qs}`, {
    next: { revalidate: 60 }, // ISR в Next.js 13+
  });

  if (!res.ok) throw new Error(`WP API error: ${res.status}`);

  return {
    posts: await res.json(),
    total: Number(res.headers.get('X-WP-Total')),
    totalPages: Number(res.headers.get('X-WP-TotalPages')),
  };
}

export async function getPostBySlug(slug: string): Promise<WPPost | null> {
  const res = await fetch(`${WP_API_URL}/posts?slug=${slug}&_embed=wp:featuredmedia,wp:term`);
  const posts = await res.json();
  return posts.length ? posts[0] : null;
}

Next.js App Router: מסלולים דינמיים

// app/blog/[slug]/page.tsx
import { getPostBySlug, getPosts } from '@/lib/wordpress';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const { posts } = await getPosts({ perPage: 100 });
  return posts.map(post => ({ slug: post.slug }));
}

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);
  if (!post) return {};
  return {
    title: post.title.rendered,
    description: post.excerpt.rendered.replace(/<[^>]+>/g, '').slice(0, 160),
  };
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);
  if (!post) notFound();
  const media = post._embedded?.['wp:featuredmedia']?.[0];
  return (
    <article className="post-single">
      {media && (
        <img src={media.source_url} alt={media.alt_text} className="post-single__cover" />
      )}
      <h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
      <div className="post-content" dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
    </article>
  );
}

// lib/wordpress.ts const WP_API_URL = process.env.NEXT_PUBLIC_WP_URL + '/wp-json/wp/v2'; export interface WPPost { id: number; slug: string; title: { rendered: string }; content: { rendered: string }; excerpt: { rendered: string }; date: string; featured_media: number; _embedded?: { 'wp:featuredmedia'?: [{ source_url: string; alt_text: string }]; 'wp:term'?: Array<Array<{ id: number; name: string; slug: string }>>; }; } export async function getPosts(params: { perPage?: number; page?: number; category?: number; search?: string; } = {}): Promise<{ posts: WPPost[]; total: number; totalPages: number }> { const qs = new URLSearchParams({ per_page: String(params.perPage ?? 12), page: String(params.page ?? 1), _embed: 'wp:featuredmedia,wp:term', ...(params.category && { categories: String(params.category) }), ...(params.search && { search: params.search }), }); const res = await fetch(`${WP_API_URL}/posts?${qs}`, { next: { revalidate: 60 }, // ISR в Next.js 13+ }); if (!res.ok) throw new Error(`WP API error: ${res.status}`); return { posts: await res.json(), total: Number(res.headers.get('X-WP-Total')), totalPages: Number(res.headers.get('X-WP-TotalPages')), }; } export async function getPostBySlug(slug: string): Promise<WPPost | null> { const res = await fetch(`${WP_API_URL}/posts?slug=${slug}&_embed=wp:featuredmedia,wp:term`); const posts = await res.json(); return posts.length ? posts[0] : null; } מקובל כאן — התוכן מגיע משרת מהימן, אבל אם המקור בשליטתך, הוסף // app/blog/[slug]/page.tsx import { getPostBySlug, getPosts } from '@/lib/wordpress'; import { notFound } from 'next/navigation'; export async function generateStaticParams() { const { posts } = await getPosts({ perPage: 100 }); return posts.map(post => ({ slug: post.slug })); } export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await getPostBySlug(params.slug); if (!post) return {}; return { title: post.title.rendered, description: post.excerpt.rendered.replace(/<[^>]+>/g, '').slice(0, 160), }; } export default async function PostPage({ params }: { params: { slug: string } }) { const post = await getPostBySlug(params.slug); if (!post) notFound(); const media = post._embedded?.['wp:featuredmedia']?.[0]; return ( <article className="post-single"> {media && ( <img src={media.source_url} alt={media.alt_text} className="post-single__cover" /> )} <h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} /> <div className="post-content" dangerouslySetInnerHTML={{ __html: post.content.rendered }} /> </article> ); } לאבטחה.

Hook מותאם אישית ל-React SPA

// hooks/usePosts.ts
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(r => r.json());

export function usePosts(category?: string, page = 1) {
  const params = new URLSearchParams({
    per_page: '12',
    page: String(page),
    _embed: '1'
  });

  if (category) params.set('categories', category);

  const { data, error, isLoading } = useSWR<WPPost[]>(
    `/wp-json/wp/v2/posts?${params}`,
    fetcher,
    { revalidateOnFocus: false }
  );

  return { posts: data ?? [], isLoading, error };
}

השתמש ב-hook זה עם דפדוף (pagination) ושלדי טעינה (skeletons). דוגמת רכיב נמצאת בתיעוד של SWR.

איך להגדיר ISR לפי דרישה לעדכוני תוכן מיידיים?

Next.js תומך בבנייה מחדש של דפים עם טריגרים. כאשר פוסט מתפרסם בוורדפרס, קוד PHP שולח בקשת POST לנקודת הקצה dangerouslySetInnerHTML ב-Next.js:

// WordPress: save_post hook и CORS filter
add_action('save_post', function (int $post_id, WP_Post $post): void {
    if ($post->post_status !== 'publish') return;
    $next_url = get_option('nextjs_revalidate_url');
    $secret = get_option('nextjs_revalidate_secret');
    if (!$next_url || !$secret) return;
    wp_remote_post("{$next_url}/api/revalidate", [
        'body' => json_encode([
            'secret' => $secret,
            'path' => '/' . $post->post_type . '/' . $post->post_name,
        ]),
        'headers' => ['Content-Type' => 'application/json'],
        'blocking'=> false,
    ]);
}, 10, 2);

add_filter('rest_pre_serve_request', function ($value) {
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    $allowed = ['https://mysite.com', 'https://www.mysite.com', 'http://localhost:3000'];
    if (in_array($origin, $allowed)) {
        header('Access-Control-Allow-Origin: ' . $origin);
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('Access-Control-Allow-Headers: Authorization, Content-Type');
    }
    return $value;
});

ובצד של Next.js אנו מטפלים:

// Next.js: app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(req: Request) {
  const { secret, path } = await req.json();
  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ error: 'Invalid secret' }, { status: 401 });
  }
  revalidatePath(path);
  return Response.json({ revalidated: true, path });
}

צינור זה מבטיח עדכוני דפים תוך שניות ללא בנייה מחדש של כל האתר. חיסכון בתעבורה — עד 40% בהשוואה לרגנרציה מלאה.

ביצועים: מה לשמור במטמון

נתונים אסטרטגיה
רשימת פוסטים ISR, revalidate: 60 שניות
פוסט בודד ISR + revalidate לפי דרישה ב-save_post
תפריט ניווט סטטי (revalidate: false)
תוצאות חיפוש SSR (ללא מטמון, הפרמטרים משתנים)
הגדרות אתר ACF סטטי או revalidate: 3600 שניות

השוואה בין REST ל-GraphQL

קריטריון REST GraphQL
גמישות שאילתות שדות קבועים בחירת נתונים נדרשים בלבד
מספר בקשות לעיתים קרובות מרובות אחת
מורכבות התקנה מינימלית דורש תוסף וסכמה
ביצועים גבוהים לדפים פשוטים טובים יותר לקינון מורכב

איך עובדת אינטגרציית וורדפרס ללא ראש

  1. ביקורת — אנו בודקים את הארכיטקטורה הנוכחית של וורדפרס, תוספים, נפח תוכן ועומס.
  2. עיצוב — אנו מגדירים נקודות קצה נדרשות, שדות ואסטרטגיית מטמון (ISR, SSG, SSR).
  3. הגדרת API — אנו מרחיבים את REST או מתקינים WPGraphQL, מוסיפים שדות מותאמים אישית (ACF).
  4. פיתוח פרונטאנד — אנו בונים לקוח טיפוסי (TypeScript), רכיבים וניתוב.
  5. אינטגרציה — אנו מחברים CORS, revalidation לפי דרישה, פורסים את וורדפרס ואת הפרונטאנד.
  6. בדיקות — אנו בודקים ביצועים (Core Web Vitals), בקשות בין-דומיין, טיפול בשגיאות.
  7. תיעוד ומסירה — אנו מקליטים ייעוץ וידאו לצוות ומוסרים את המאגר (repository).

לוח זמנים: אינטגרציה בסיסית — 5 עד 7 ימי עבודה; תרחישים מורכבים (GraphQL, ACF, ISR לפי דרישה) — עד 15 ימים. אנו מספקים הערכה מדויקת לאחר הביקורת.

פרטי הגדרת CORS

כאשר הדומיינים של וורדפרס והפרונטאנד שונים, אנו מוסיפים את הפילטר DOMPurify שמגדיר כותרות למקורות מורשים. זהו נוהג סטנדרטי המתואר בתיעוד של REST API. אם אתה צריך הגדרה לא סטנדרטית — אנו יכולים להתאים אותה לפרויקט שלך.

כמה עולה אינטגרציית וורדפרס ללא ראש?

העלות מחושבת באופן פרטני לפי נפח תוכן, מספר סוגי פוסטים, צורך ב-GraphQL ומורכבות ניתוב. בממוצע, התקציב דומה למשכורת חודשית של מפתח זוטר, והחיסכון באחסון ובזמן פיתוח מחזיר את ההשקעה תוך חצי שנה.

רוצה הערכה מדויקת? הזמן ביקורת על הפרויקט שלך — ננתח את הארכיטקטורה ונציע את הפתרון האופטימלי. צור קשר דרך הטופס באתר או כתוב לטלגרם.