הבלוג הראשון שלי

הצגת הבעיה:
הפלטפורמה חסרה ערוץ שיווק תוכן. בלוג מאפשר לכל טננט לפרסם מאמרים מרובי-שפות, מותאמים ל‑SEO, שנכתבים ב‑Strapi CMS ומוצגים באפליקציית SvelteKit — מה שמניע תנועה אורגנית, מבסס סמכות, ותומך בצנרת ה‑i18n הקיימת ל‑13 לוקאלים.
קריטריוני הצלחה
- פוסטים בבלוג שנכתבו ב-Strapi מוצגים בפרונטאנד בתוך המיתוג של ה-tenant
- פוסטים מתורגמים אוטומטית לכל 13 ה-locales דרך ה-webhook pipeline הקיים (ללא שינויים בתשתית התרגום)
- עמודי הבלוג מדורגים במנועי חיפוש (structured data, sitemap, hreflang, og:article)
- Tenants ללא דגל הפיצ'ר
blogאינם רואים נתיבי בלוג (404)
סיפורי משתמש
- בתור כותב תוכן, אני רוצה לכתוב פוסטים לבלוג ב‑Strapi עם מרקडाउन עשיר, תמונות והטמעת וידאו, כדי שאוכל לפרסם תוכן בלי לגעת בקוד
- בתור מבקר באתר, אני רוצה לעיין בפוסטים בבלוג שמסוננים לפי קטגוריה, כדי שאוכל למצוא תוכן רלוונטי
- בתור מבקר באתר, אני רוצה תוכן עניינים בפוסטים ארוכים, כדי שאוכל לנווט במהירות
- בתור מבקר באתר, אני רוצה לקרוא פוסטים בשפה שלי, עם תרגום אוטומטי מאנגלית
- בתור מבקר באתר, אני רוצה לשתף פוסטים ברשתות חברתיות
- בתור מבקר באתר, אני רוצה לגלות פוסטים קשורים ולהכיר את כותב הפוסט
- בתור מנהל SEO, אני רוצה שלכל פוסט יהיו תגיות מטא מתאימות, נתונים מובנים ורישום במפת האתר
דרישות פונקציונליות
חייב להיות (MVP)
- Strapi content types:
blog-posts,blog-authors,blog-categories— לכולם שדות tenant, locale, inherit - עמוד רשימת בלוג (
/blog) — גריד פוסטים מדופדף (paginated), פילטר קטגוריה דרך query param - עמוד פוסט (
/blog/[slug]) — רינדור מלא של markdown עם ToC, כרטיס מחבר, פוסטים קשורים, שיתוף ברשתות חברתיות - עמוד קטגוריה (
/blog/category/[slug]) — רשימה מסוננת שממיחה (reuses) את אותם קומפוננטים של הרשימה - עמוד מחבר (
/blog/author/[slug]) — ביוגרפיה של המחבר + הפוסטים שלו/שלה - תוכן עניינים (Table of Contents) — נוצר אוטומטית מכותרות h2/h3, סיידבר "דביק" בדסקטופ, מתקפל במובייל, הדגשת כותרת פעילה דרך Intersection Observer
- הטמעות YouTube/Vimeo — כתובות URL חשופות ב-markdown מומרות אוטומטית ל‑iframes רספונסיביים (youtube-nocookie.com לשמירה על פרטיות)
- זמן קריאה — מחושב בצד השרת מתוך ספירת מילים (Math.ceil(words / 200))
- כפתורי שיתוף ברשתות חברתיות — X, Facebook, LinkedIn, אימייל, העתקת קישור (Web Share API כשזמין)
- פוסטים קשורים — מאותה קטגוריה, ללא הפוסט הנוכחי, מוגבל ל‑3, ממוינים לפי publishedAt בסדר יורד
- RSS feed (
/blog/rss.xml) — פורמט RSS 2.0, locale דרך query param - SEO — SeoComponent לכל פוסט, תגיות og:article, סכמת BlogPosting ב‑JSON-LD, כתובות URL קנוניות (canonical)
- Sitemap — הוספת כתובות URL דינמיות של פוסטים/קטגוריות/מחברים ל‑sitemap הקיים כולל hreflang
- Feature flag —
blog: booleanבתוך TenantConfig.features, ה‑layout guard מחזיר 404 כשהפיצ'ר כבוי - i18n — מפתחות הודעות Paraglide לכל מחרוזות ה‑UI (ניווט, תוויות, פאג'ינציה), תוכן Strapi מתורגם אוטומטית דרך ה‑webhook הקיים
- פאג'ינציה — מבוססת עמודים באמצעות הפאג'ינציה המובנית של Strapi (9 פוסטים בכל עמוד)
- פוסט מומלץ (Featured post) — שדה בוליאני
featuredעל blog-posts; פוסט מומלץ מוצג ככרטיס hero בחלק העליון של עמוד הרשימה/blog(כרטיס גדול, ברוחב מלא, מעל הגריד)
נחמד שיהיה (Post-MVP)
- חיפוש טקסט חופשי על פני כל פוסטי הבלוג
- סדרות פוסטים / מאמרים מרובי חלקים
- סרגל התקדמות קריאה
- תגובות (כנראה דרך הטמעת שירות צד שלישי)
- קריאת הרשמה לניוזלטר (CTA) בתוך הפוסטים
- ספירת צפיות / אינטגרציית אנליטיקס
דרישות טכניות
מודל נתונים (סוגי תוכן ב‑Strapi)
blog-posts (api::blog-post.blog-post):
| Field | Type | Localized | Notes |
|---|---|---|---|
| title | Text (required) | Yes | מותאם לפי תצורת התרגום |
| slug | UID (from title) | No | לא נכלל בתרגום |
| excerpt | Text | Yes | עבור כרטיסים + תיאור מטא |
| body | Rich Text (Markdown) | Yes | נרנדר דרך marked |
| featuredImage | Media (single) | No | URL + alternativeText |
| category | Relation (manyToOne -> blog-category) | No | |
| author | Relation (manyToOne -> blog-author) | No | |
| featured | Boolean (default false) | No | פוסט נבחר שמוצג ככרטיס הירו ברשימה |
| seo | Component (shared.seo) | Yes | שימוש חוזר ב‑SeoComponent הקיים |
| tenant | Text | No | מסנן tenant סטנדרטי |
| inherit | Boolean (default true) | No | דגל תרגום אוטומטי |
blog-authors (api::blog-author.blog-author):
| Field | Type | Localized | Notes |
|---|---|---|---|
| name | Text (required) | Yes | |
| slug | UID (from name) | No | |
| bio | Rich Text (Markdown) | Yes | |
| avatar | Media (single) | No | |
| socialLinks | JSON | No | { twitter?, linkedin?, website? } |
| tenant | Text | No | |
| inherit | Boolean (default true) | No |
blog-categories (api::blog-category.blog-category):
| Field | Type | Localized | Notes |
|---|---|---|---|
| name | Text (required) | Yes | |
| slug | UID (from name) | No | |
| description | Text | Yes | |
| color | Text | No | צבע Hex לתגיות UI |
| tenant | Text | No | |
| inherit | Boolean (default true) | No |
ממשקי TypeScript
ראו src/lib/services/strapi-types.ts עבור BlogPost, BlogAuthor, BlogCategory.
ראוטים
הכול תחת src/routes/(marketing)/blog/:
| Route | Server Load | Purpose |
|---|---|---|
blog/+layout.server.ts |
Feature guard + טעינת קטגוריות | שער + נתוני ניווט |
blog/+page.server.ts |
פוסטים בדפדוף (paginated), מסנן אופציונלי ?category= |
רשימה |
blog/[slug]/+page.server.ts |
פוסט לפי slug + פוסטים קשורים + חישוב זמן קריאה | דף פרטים |
blog/category/[slug]/+page.server.ts |
פוסטים מסוננים לפי קטגוריה | רשימת קטגוריות |
blog/author/[slug]/+page.server.ts |
מחבר + הפוסטים שלו | דף מחבר |
blog/rss.xml/+server.ts |
RSS 2.0 XML, פרמטר ?locale= |
פיד |
רכיבי UI
הכול ב‑src/lib/components/blog/:
| Component | Props | Notes |
|---|---|---|
BlogPostCard.svelte |
post: BlogPost |
כרטיס עם תמונה, תגית קטגוריה, כותרת, תקציר, מחבר, תאריך, זמן קריאה |
BlogPostContent.svelte |
body: string |
המרת Markdown ל‑HTML דרך marked עם הרחבת הטמעת וידאו + מזהי כותרות |
TableOfContents.svelte |
entries: TocEntry[] |
סרגל צד דביק, Intersection Observer למצב פעיל, מתקפל במובייל |
AuthorCard.svelte |
author: BlogAuthor |
אווטאר, שם, ביוגרפיה, קישורים חברתיים |
SocialShare.svelte |
url: string, title: string |
כפתורי שיתוף עם fallback ל‑Web Share API |
RelatedPosts.svelte |
posts: BlogPost[] |
גריד של BlogPostCard |
BlogPagination.svelte |
pagination: StrapiPagination, basePath: string |
קודם/הבא + מספרי עמודים |
אבטחה ואימות
- הבלוג הוא ציבורי (ללא צורך באימות) — תוכן שיווקי
- גישת קריאה ל־Strapi API באמצעות טוקן
STRAPI_JWTהקיים - הטמעת וידאו משתמשת ב־
youtube-nocookie.comלצורך עמידה בדרישות GDPR
מקרים קיצוניים וטיפול בשגיאות
- פוסט לא נמצא: 404 דרך SvelteKit
error() - אין פוסטים בקטגוריה: מצב ריק עם הודעת
blog_no_posts - תמונת נושא חסרה: הכרטיס מוצג ללא תמונה
- תרגום לא זמין:
loadContentWithFallback()נופל חזרה לאנגלית - שפות מימין לשמאל: הפריסה מכבדת
dir="rtl"(ar, he, ur)
תוכנית בדיקות E2E
קבצי הבדיקות נמצאים ב-tests/blog/. עקבו אחרי התבניות הקיימות: פרמטריזציה לפי גדלי מסך, Axe לנגישות (a11y), networkidle ממתין לתוכן מ-Strapi.
בדיקות מסלול קריטי
- טעינת רשימת בלוג —
/blogמציג גריד של כרטיסי פוסטים עם כותרת, תקציר, תג קטגוריה, כותב, תאריך וזמן קריאה - פאג’ינציה עובדת —
/blog?page=2מציג את עמוד התוצאות השני; כפתורי הקודם/הבא מנווטים נכון; בעמוד 1 אין כפתור "Previous" - פילטר קטגוריה — לחיצה על צ’יפ של קטגוריה מסננת את הפוסטים; ה-URL מתעדכן ל-
?category=<slug>; צ’יפ "All" מנקה את הפילטר - עמוד פוסט נטען —
/blog/[slug]מציג כותרת, תמונה ראשית, גוף מעוצב, כרטיס מחבר, זמן קריאה ותאריך פרסום - תוכן עניינים — עמוד פוסט מייצר TOC מתוך כותרות h2/h3; לחיצה על פריט ב-TOC גוללת לכותרת המתאימה
- פוסטים קשורים — עמוד פוסט מציג עד 3 פוסטים קשורים מאותה קטגוריה (ללא הפוסט הנוכחי)
- עמוד קטגוריה —
/blog/category/[slug]מציג שם קטגוריה, תיאור ורק פוסטים מאותה קטגוריה - עמוד מחבר —
/blog/author/[slug]מציג ביוגרפיית מחבר, אוואטר, לינקים חברתיים והפוסטים שלו - כפתורי שיתוף — כפתורי שיתוף נראים בעמוד פוסט; "Copy link" מעתיק את ה-URL ומציג טקסט אישור
- פיד RSS —
GET /blog/rss.xmlמחזיר XML תקין עםContent-Type: application/rss+xmlומכיל רשומות פוסטים - ניווט — קישור הבלוג בתפריט האתר מנווט ל-
/blog; פירורי לחם מציגים היררכיה נכונה - פוסט מומלץ —
/blogמציג את הפוסט המומלץ ככרטיס הירו גדול מעל הגריד הרגיל
בדיקות קצה ושגיאות
- פוסט לא נמצא —
/blog/nonexistent-slugמחזיר עמוד 404 - קטגוריה ריקה — קטגוריה ללא פוסטים מציגה את הודעת מצב הריק
blog_no_posts - ללא תמונה ראשית — כרטיס פוסט מוצג בצורה תקינה גם בלי תמונה (אין
<img>שבור) - חוסר במחבר/קטגוריה — פוסט ללא מחבר או קטגוריה מוצג ללא האלמנטים האלו (ללא קריסה)
- דגל פיצ’ר כבוי — כאשר
blog: false, /blogמחזיר 404
בדיקות רוחב
- i18n: החלפת לוקל — נווט ל-
/fr/blog, ודא שמחרוזות ה-UI (כותרת עמוד, "Read more", תוויות פאג’ינציה) בצרפתית; תוכן הפוסט נופל חזרה לאנגלית אם לא תורגם - i18n: פריסת RTL — נווט ל-
/ar/blogו-/ar/blog/[slug], ודא שה-dir="rtl"מוגדר ושהפריסה מוצגת נכון (TOC בצד הנכון) - SEO: מטא טגים — עמוד פוסט מכיל
og:type=article,og:title,og:description,article:published_timeוקישור canonical - SEO: JSON-LD — עמוד פוסט מכיל
<script type="application/ld+json">עם@type: BlogPosting, headline, author, datePublished - SEO: מפת אתר —
/sitemap.xmlכולל כתובות URL של פוסטים עם hreflang alternates - נגישות — הרצת בדיקות Axe על
/blog,/blog/[slug],/blog/author/[slug]גם במובייל וגם בדסקטופ (בהתאם לתבנית בדיקות a11y הקיימת) - ניווט עם מקלדת — פריטי TOC וכפתורי פאג’ינציה ניתנים לפוקוס ולהפעלה באמצעות מקלדת; אינדיקטורי הפוקוס נראים לעין
מחוץ לתחום
- תגובות לפוסטים בבלוג
- חיפוש בטקסט מלא
- אינטגרציית ניוזלטר בתוך פוסטים
- אנליטיקות/ספירת צפיות
- תבניות פוסט מותאמות אישית לכל טננט
Ludo Fourrage
לודו הוא מחנך ויזם בתחום הטכנולוגיה, שמונע מאמונה פשוטה: חינוך איכותי צריך להיות נגיש לכולם.