ספרייה, קטלוג או גלריה שיושבים בתוך רשימה אחת באפליקציית React הם תוכן שגוגל לא רואה. לפריט בתוך לולאה אין כתובת משלו, אין כותרת, ואי אפשר להגיע אליו מחיפוש. ה-Skill הזה נותן את התבנית שמייצרת עמוד אמיתי לכל פריט מתוך קובץ נתונים, בעזרת סקריפט רינדור מקדים ובלי לעבור ל-SSR ובלי framework חדש. הוא כולל גם את השאלה שרוב האנשים מדלגים עליה, מתי פריט בכלל ראוי לעמוד משלו, כי מאתיים עמודים דלילים פוגעים בכל האתר ולא רק בעצמם. זהו Skill ל-Claude: תיקייה עם קובץ SKILL.md, שנטענת אוטומטית כשהמשימה מתאימה לתיאור שבראש הקובץ. בניגוד לפרומפט שמדביקים בכל פעם מחדש, Skill מותקן פעם אחת וממשיך לעבוד לבד.
בניית אתרים · SEO · React · Vite · רינדור מקדים · קטלוג
---
name: spa-page-factory
description: "Generate hundreds of unique indexable pages from a data file in a Vite or React SPA, using a prerender script instead of SSR. Use when adding a library, catalog, gallery, glossary or directory to a site, when content lives in one long list with no individual URLs, or when asked to make SPA content visible to Google."
---
# ממערך נתונים למאות עמודים שגוגל סורק
## הבעיה
פריט בתוך `map` הוא לא עמוד. אין לו כתובת, אין לו כותרת, ואי אפשר
להביא אליו אף אחד מחיפוש.
התבנית הזו הפיקה בפרויקט אחד יותר משבע מאות כתובות מארבעה מקורות נתונים,
בלי SSR. הוספת ספרייה נוספת עלתה בקובץ אחד ולולאה אחת.
## שלושה קבצים לכל ספרייה
```
src/library/<name>/index.ts נתונים בלבד. בלי React
src/lib/<name>-seo.ts כותרת, תיאור, פסקת פתיחה, פריטים קרובים
src/pages/<Name>Item.tsx העמוד. משתמש באותן פונקציות
```
**הכלל שחייב להישמר: קובצי הנתונים וה-SEO לא מייבאים React.**
זה לא סגנון. סקריפט הרינדור רץ ב-Node, ואם הקובץ גורר אייקונים או
קומפוננטות עצלות הוא גורר איתם את כל עץ התלויות ונשבר. בלי הכלל הזה
תיאלצו לשכפל את הטקסטים לתוך הסקריפט, והם ייפרדו זה מזה תוך שבוע.
## לטעון את הנתונים מתוך הסקריפט
הנתונים כתובים ב-TypeScript ומשמשים גם את העמוד. במקום לשכפל אותם, לארוז
ולטעון בזמן הבנייה:
```js
async function loadFromSource(entry) {
const out = join(root, 'scripts/.prerender-data.mjs')
await build({
entryPoints: [join(root, entry)],
bundle: true, platform: 'node', format: 'esm',
outfile: out, logLevel: 'error',
})
const mod = await import(pathToFileURL(out).href + `?t=${Date.now()}`)
rmSync(out, { force: true })
return mod
}
```
## הלולאה
```js
for (const item of ITEMS) {
const path = `/library/${item.slug}`
writePage(path, pageHtml({
title: itemTitle(item),
description: itemDescription(item),
path,
heading: { h1: item.name, p: itemIntro(item) },
extra: `<pre>${esc(item.body)}</pre>`,
jsonLd: [schemaFor(item), breadcrumbs([...])],
}))
}
```
`writePage` כותב `dist/<path>/index.html`. שרת סטטי מגיש את הקובץ אם
הוא קיים, ואחרת נופל להפניית ה-SPA. אין צורך בקונפיגורציה.
**להוסיף את הכתובות למפת האתר באותה לולאה**, אחרת נבנו עמודים שאיש לא
יודע עליהם.
## מה הופך עמוד לעמוד ולא לשלד
- **התוכן הייחודי נכתב ל-HTML הסטטי**, ולא רק מוזרק על ידי React. מה
שהמבקר בא בשבילו חייב להיות שם גם בלי JavaScript.
- **פסקת פתיחה תמיד**, גם כשלפריט יש הסבר משלו.
- **פריטים קרובים וקישורים משלימים.** זה מה שהופך חמש מאות עמודים לרשת
ולא לחמש מאות מבויי סתום.
- **JSON-LD לפי הסוג:** `HowTo` להוראות, `FAQPage` לשאלות, ותמיד
`BreadcrumbList`.
## שמות כפולים הם תוכן משוכפל
שני פריטים עם אותו שם מייצרים שני עמודים שמתחרים זה בזה, ואף אחד מהם לא
עולה. הפתרון מבני ולא רשימת תיקונים ידנית:
```js
const mark = item.tags.find((t) => !others.some((o) => o.tags.includes(t)))
if (mark) DISPLAY_NAME.set(item.slug, `${item.name} (${mark})`)
```
## בדיקות ברמת הנתונים
ספרייה מייצרת עמודים, ולכן טעות בנתונים אינה באג בממשק אלא עמוד שבור
שגוגל סורק. הכל בלי דפדפן:
- slug ייחודי
- ההסבר ארוך מספיק לעמוד, מעל מאה ועשרים תווים
- כל קישור לפריט משלים מצביע על משהו שקיים. רשימה ידנית הופכת לקישור מת
בשקט ברגע ששם משתנה
## כמה עמודים זה יותר מדי
**שישים ושניים פריטים בני מילה אחת אינם שישים ושניים עמודים.** עמוד שכל
תוכנו מילה ושורת הסבר הוא בדיוק התוכן הדליל שפוגע בדירוג של כל האתר.
פריט מקבל עמוד רק אם יש לו מה למלא אותו: הסבר, שימוש, מגבלות, וקישורים
החוצה. אחרת מקומו בעמוד אחד עשיר שאפשר לחפש בתוכו.
כליקס לא הצליח להיטען
נסו קודם לרענן את העמוד. אם בדיוק עלתה גרסה חדשה, רענון פותר את זה מיד.
אם הרענון לא עזר, ייתכן שהדפדפן ישן מדי: כליקס מריץ את הכלים על המכשיר שלכם ולא בשרת, ולכן הוא זקוק לדפדפן מ-2022 ומעלה. מה עושים: לעדכן את Chrome או את Firefox, ולפתוח שוב את klixtools.com.