post.md
23 באוק׳ 2026 · 3 דקות קריאה
איך האתר הזה בנוי
סיור קצר בארכיטקטורה שמאחורי cmkrw.com, מפריסת הטרמינל ועד Markdown, תרשימים ודיווח שגיאות.
- nextjs
- design
תוכן עניינים
האתר הזה קטן בכוונה: עמוד אחד, בלוג אחד וכמה שפחות JavaScript. כך החלקים משתלבים, למקרה שתרצו לבנות משהו דומה.
הטרמינל הוא הפריסה
האתר בנוי כמו אפליקציית טרמינל. סרגל לשוניות עובר לאורך החלק העליון, שורת מצב נמצאת בתחתית, וכל מה שביניהם הוא חלונית: תיבה עם מסגרת וכותרת שיושבת בקצה העליון שלה. רכיב קטן אחד, Pane, מצייר את כולן.
הבלוג כתצוגת רשימה-ופרטים: הרשימה נשארת במקומה בזמן שהפוסט מתחלף.
הניווט פועל באותו רעיון. בבלוג רשימת הפוסטים נשארת בצד, והפוסט נפתח לצדה, כך שמעבר בין פוסטים לא דורש קישור חזרה. קיצורי המקלדת פועלים כמו בטרמינל, ואפשר למיין את הטבלה הזו בלחיצה על כותרת עמודה:
| 1 | פתיחת דף הבית | גלובלי |
| 2 | פתיחת הבלוג | גלובלי |
| j | גלילה למטה | גלובלי |
| k | גלילה למעלה | גלובלי |
| g g | קפיצה להתחלה | גלובלי |
| G | קפיצה לסוף | גלובלי |
| n | פוסט ישן יותר | בלוג |
| p | פוסט חדש יותר | בלוג |
| d | החלפת ערכת נושא | גלובלי |
| ? | הצגת קיצורים | גלובלי |
התנועה מבוססת בעיקר על CSS. הפקודה בחלק העליון נכתבת באנימציית steps(), החלוניות עולות לתצוגה בזמן הגלילה, והקו שמתחת לסרגל הלשוניות ואחוז הגלילה בשורת המצב עוקבים אחר מיקום הגלילה. הכול כבוי תחת prefers-reduced-motion.
כתיבת פוסטים
פוסטים הם קובצי Markdown רגילים ב-content/blog/<slug>/<locale>.md, המוצגים באמצעות TanStack Markdown. תרגום חסר חוזר לקובץ באנגלית, ו-draft: true מונע מפוסט להופיע בגרסת הייצור.
שלושה כלים של TanStack עושים את השאר:
- Markdown מנתח ומציג את הפוסט כרכיבי React, בלי מחרוזת HTML באמצע.
- Highlight צובע בלוקי קוד. רק השפות שאני רושם נכללות בחבילה, והצבעים מגיעים מאותם משתני CSS כמו שאר ערכת הנושא.
- Charts ו-Table משובצים בהערה של שורה אחת שמציינת קובץ JSON לצד הפוסט.
לבלוקי קוד יכולה להיות כותרת של קובץ:
export function createEmbeds(slug: string, locale: string) {
return {
chart: ({ data = "", kind = "bar", x = "x", y = "y", title = "Chart" }) => (
<TuiChart
rows={getPostDataset(slug, data, locale)}
kind={kind}
x={x}
y={y}
label={title}
/>
),
}
}גם טבלאות Markdown רגילות עובדות, ומקבלות את אותו עיצוב:
| שכבה | תיקייה | אחריות |
|---|---|---|
| נתיבים | app/ | עמודים, מטא-נתונים, אייקונים ותמונות |
| ממשק | components/tui/ | חלוניות, סרגל ניווט, שורת מצב, מיפוי מקשים |
| תכונות | features/ | מקטעי דף הבית והבלוג |
| כלי עזר | lib/, hooks/ | מטא-נתונים, תצפיתיות, קלט מקשים |
כך נראה מספר קובצי המקור בכל תיקייה ברגע הכתיבה:
הצגת הנתונים
| folder | files |
|---|---|
| app | 17 |
| components | 16 |
| lib | 16 |
| features | 15 |
| i18n | 4 |
| hooks | 3 |
| types | 1 |
אייקונים וכרטיסי שיתוף שנוצרים אוטומטית
Next.js 16 בונה תמונות מטא-נתונים מקבצים בתיקיית app/:
icon.tsxו-apple-icon.tsxמציירים את האייקון של האתר ואת האייקון של iOS מקובץ SVG יחיד,assets/logo-icon.svg.opengraph-image.tsxמצייר את כרטיס השיתוף של דף הבית, ועוד אחד מצייר כרטיס לכל פוסט.
אין קובץ favicon.tsx. התיעוד מדגיש שאי אפשר ליצור favicon באופן דינמי, ולכן icon.tsx עושה את העבודה. כל אלה חולקים סט קטן של אבני בניין, כך ששינוי סמל המותג משנה את האייקון ואת כל כרטיסי השיתוף בבת אחת.
מכשור
שני קבצים מטפלים בתצפיתיות. instrumentation.ts רץ בשרת ומדווח על שגיאות בקשות. instrumentation-client.ts רץ לפני ההידרציה ועוקב אחר שגיאות לקוח, מדדי ווב ושינויי נתיב. שניהם מעבירים אירועים למדווח שמפזר אותם ליעדים:
export const serverReporter = createReporter([
consoleSink,
...(endpoint ? [endpointSink(endpoint)] : []),
])אירועי לקוח נשלחים אל /api/observability, שרושם אותם ביומן. כשיהיה צורך לחבר אספן אמיתי, צריך רק להוסיף יעד אחד, בלי לגעת באף אחד משני קובצי הכניסה.
תרגומים
האתר זמין בעשר שפות, כולל ערבית ועברית הנקראות מימין לשמאל. כל מחרוזת נמצאת בקובץ הודעות לכל שפה; אם מפתח חסר, ההודעות של שפת ברירת המחדל מתמזגות מתחת, כך שעמוד אף פעם לא נשאר ריק. פוסטים פועלים באותו אופן: לכל שפה קובץ משלה, ואם הוא חסר משתמשים באנגלית.