ניהלתי את האתר הזה בעבר על דומיין מותאם שהיה מחובר לגרסת Google Sites הקלאסית שנכללה ב-Google Apps (שקדמה ל-Google Workspace). Google הפסיקה את השירות ההוא בכל העולם ב-2021, ולכן השארתי את https://www.ixam.net מוזנח זמן רב. בסוף החלטתי שכדאי להחזיר את הדומיין לחיים. רציתי ארכיטקטורה חסרת-שרת שלא תלויה בפלטפורמה שעלולה להיעלם בן רגע, ויצאתי למסע השיפוץ יחד עם ChatGPT.

חשבתי ש-ChatGPT יהפוך את הפרויקט לטיול נוח, אבל המציאות הייתה אחרת. המשכתי לשכוח דרישות, נתקענו בלופים של ניפוי באגים, ובסוף ישבתי על ריפוזיטוריז כמו https://github.com/patrickgrey/website/ כדי להשתחרר. זה היה מאמץ לא פשוט, אבל סיימנו אותו כעבודה משותפת.

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

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

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


מטרות

  • להציג את הבלוג ב-https://www.example.com/
  • להיכנס ל-Decap CMS ב-https://www.example.com/admin/ וליצור פוסטים
  • לדאוג שפוסטים חדשים ייקומיטו ל-GitHub וייפרסו אוטומטית ל-Cloudflare
  • לשמור על עלויות תפעול נוספות אפסיות (עלות רישום הדומיין מחוץ להיקף המדריך)

0. דרישות מוקדמות ומבנה תיקיות

0-1. דרישות מוקדמות

  • דומיין (למשל example.com) כבר רשום בבעלותך

  • ניתן לעבוד ב-Windows או macOS (מצורפות דוגמאות פקודה לשניהם)

  • השירותים שבהם נשתמש (חבילות החינם מספיקות)

0-2. דוגמה למבנה ריפוזיטורי

hugo.toml
content/
  blog/
  posts/
data/
functions/
  api/
    auth.js
    callback.js
layouts/
  _default/
    baseof.html
    index.html
    list.html
    single.html
static/
  _headers
  admin/
    config.yml
    index.html
  css/
    main.css

למבנה הזה יש את התפקידים הבאים:

  • hugo.toml — הגדרות כלליות של האתר (להחליף את baseURL בכתובת הפרודקשן)
  • functions/api/*.js — פונקציות של Cloudflare Pages (הנתיבים /api/auth ו-/api/callback ל-OAuth מול GitHub)
  • layouts/_default/*.html — תבניות Hugo
  • static/admin/ — ממשק והגדרות של Decap CMS
  • static/css/main.css — עיצוב
  • static/_headers — כותרות HTTP אופציונליות עבור Cloudflare Pages

לאורך המדריך אני מסמן נקודות שדורשות התאמה לסביבה כתחנות “החלף כאן”.


1. הכנות

1-1. יצירת חשבון GitHub

  • הירשם ל-GitHub דרך הדפדפן

1-2. פתיחת חשבון Cloudflare

  • הירשם ל-Cloudflare דרך הדפדפן

1-3. התקנת Git ו-Hugo

  • Windows (PowerShell)

    PS C:\Users\alice> winget install Git.Git
    PS C:\Users\alice> winget install Hugo.Hugo.Extended
    
  • macOS (Terminal)

    mac:~ dev$ brew install git
    mac:~ dev$ brew install hugo
    

2. התהליך העיקרי (עד הפריסה הראשונה)

2-1. הכנת הריפוזיטורי מקומית

  1. צור ריפוזיטורי ריק ב-GitHub

    • נתיב בממשק: GitHub > New repository
    • שם לדוגמה: my-hugo-blog
  2. שיבט אותו למחשב המקומי

    • Windows

      PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      PS C:\work> cd .\my-hugo-blog
      
    • macOS

      mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      mac:~/work dev$ cd my-hugo-blog
      
  3. מקם בתוך הריפוזיטורי את מבנה התיקיות והקבצים שהוצג לעיל

רשימת החלפה (שינויים חובה)

  • hugo.toml

    baseURL = "https://www.example.com"   # ← החלף בכתובת הפרודקשן שלך (למשל https://www.example.com)
    languageCode = "ja-jp"
    title = "Example Blog"
    publishDir = "public"
    
    [permalinks]
      blog = "/blog/:year/:month/:slug/"
    
  • static/admin/config.yml

    backend:
      name: github
      repo: <YOUR_GH_USERNAME>/my-hugo-blog   # ← החלף בריפוזיטורי ה-GitHub שלך
      branch: master                          # ← שם ברירת המחדל של הסניף (main או master)
      base_url: https://www.example.com       # ← החלף בכתובת הפרודקשן שלך
      auth_endpoint: /api/auth                # ← נקודת הקצה של הפונקציה (קבוע)
    media_folder: static/uploads
    public_folder: /uploads
    

הערה: functions/api/auth.js ו-functions/api/callback.js ניטרליים לסביבה כי הם משתמשים ב-url.origin, ולכן אין צורך לערוך אותם.

  1. בצע קומיט ראשון ודחיפה

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + DecapCMS + CF Pages"
      PS C:\work\my-hugo-blog> git push -u origin master
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + DecapCMS + CF Pages"
      mac:~/work/my-hugo-blog dev$ git push -u origin master
      

שמור את הערכים שמופיעים בשלב הבא כמשתני סביבה ב-Cloudflare Pages.


2-2. יצירת אפליקציית OAuth ב-GitHub (לכניסה אל Decap CMS)

  1. נתיב בממשק: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App

  2. מלא את הפרטים:

    • שם האפליקציה: DecapCMS for my-hugo-blog
    • כתובת הבית: https://www.example.com
    • Authorization callback URL: https://www.example.com/api/callbackקריטי
  3. לאחר יצירת האפליקציה, רשום לעצמך:

    • Client ID
    • Client Secret (צור סוד חדש ושמור אותו)

ערכים אלה ישמשו כמשתני סביבה ב-Cloudflare Pages.


2-3. יצירת פרויקט Cloudflare Pages

  1. נתיב בממשק: Cloudflare dashboard > Pages > Create a project > Connect to Git

  2. התחבר ל-GitHub ובחר את הריפוזיטורי my-hugo-blog

  3. הגדרות Build:

    • Framework preset: None (או לאפשר ל-Cloudflare לזהות אוטומטית)

    • Build command: hugo

    • Build output directory: public

    • משתני סביבה:

      • HUGO_VERSION = 0.128.0 (לדוגמה—יישר עם הגרסה המקומית שלך לראש שקט)
      • GITHUB_CLIENT_ID = הערך מהסעיף הקודם
      • GITHUB_CLIENT_SECRET = הערך מהסעיף הקודם
  4. לחץ על Save and Deploy והמתן לפריסה הראשונה

    • פריסה מוצלחת תנפק כתובת תצוגה מקדימה *.pages.dev

2-4. הגדרת הדומיין המותאם www.example.com

  1. נתיב בממשק: Cloudflare > Pages > (project) > Custom domains > Set up a custom domain

  2. הזן www.example.com

  3. אם הדומיין אינו מנוהל במלואו ב-Cloudflare:

    • העתק את פרטי רשומת ה-DNS שמופיעים ב-Cloudflare (לדוגמה, CNAME www -> <project>.pages.dev)
    • הגדר את הרשומות אצל הרשם בהתאם לתיעוד שלו
  4. לאחר שהשינוי ב-DNS מופץ, ודא שהכתובת https://www.example.com/ מציגה את האתר

טיפ: אם הדומיין כבר מנוהל ב-Cloudflare, לחיצה אחת מוסיפה אוטומטית את רשומות ה-DNS הדרושות.


2-5. התחברות לממשק הניהול של Decap CMS

  1. פתח את https://www.example.com/admin/
  2. לחץ על Login with GitHub ואשר את בקשת ה-OAuth
  3. בפעם הראשונה GitHub יבקש Authorize — אשר את ההרשאה

אם ההתחברות נכשלת, בדוק שוב את כתובת החזרה של OAuth ואת משתני הסביבה GITHUB_CLIENT_ID ו-GITHUB_CLIENT_SECRET ב-Cloudflare.


3. תפעול שוטף (כתיבה, סנכרון מקומי ושינוי תבניות)

3-1. יצירת פוסטים מתוך ה-CMS

  1. גלוש אל https://www.example.com/admin/
  2. בתפריט הצד בחר BlogNew blog
  3. מלא את Title, Publish Date, Description ו-Body
  4. לחץ על Publish כדי לקמפט ל-GitHub → Cloudflare יפרוס אוטומטית

קבצי ה-Markdown שנוצרים יופיעו תחת content/blog/.

3-2. משיכת התוכן העדכני למחשב לאחר פרסום דרך ה-CMS

  • Windows

    PS C:\work\my-hugo-blog> git pull origin master
    
  • macOS

    mac:~/work/my-hugo-blog dev$ git pull origin master
    

פעולה זו שומרת על הריפוזיטורי המקומי מסונכרן כדי שתוכל לערוך תבניות או CSS בבטחה.

3-3. שיפור עיצובים ותבניות מקומית

  1. הפעל את שרת הפיתוח המקומי

    • Windows

      PS C:\work\my-hugo-blog> hugo server -D
      
    • macOS

      mac:~/work/my-hugo-blog dev$ hugo server -D
      
    • פתח את http://localhost:1313/ בדפדפן כדי לצפות בתצוגה מקדימה

  2. נקודות התאמה נפוצות

    • layouts/_default/baseof.html — תוכן ה-<head>, הכותרת והפוטר
    • layouts/_default/index.html — רשימת הפוסטים האחרונים בעמוד הבית
    • layouts/_default/single.html — תבנית גוף המאמר
    • static/css/main.css — צבעים, פונטים, ריווח
  3. בצע קומיט ודחיפה לשינויים

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts"
      PS C:\work\my-hugo-blog> git push
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts"
      mac:~/work/my-hugo-blog dev$ git push
      
    • Cloudflare יפרוס אוטומטית בתוך שניות עד דקות

3-4. טיפים לניהול סניפים

  • ודא שסניף ברירת המחדל (main או master) זהה בין static/admin/config.yml להגדרות הפרויקט ב-Cloudflare
  • כאשר פותחים Pull Request ב-GitHub, Cloudflare Pages יוצר סביבת תצוגה מקדימה אוטומטית

4. שדרוגים אופציונליים

4-1. דוגמה ל-static/_headers (כיבוי קאש לממשק הניהול)

/admin/*
  Cache-Control: no-store

4-2. Robots וקובץ Sitemap

  • הוסף static/robots.txt כדי לשלוט בזחלנים
  • הוסף את outputs ל-hugo.toml אם ברצונך בפלטים נוספים כמו RSS או sitemap

5. מלכודות נפוצות ופתרונות

  • ההתחברות ל-CMS נתקעת בלופ
    → בדוק שכתובת החזרה של GitHub OAuth היא בדיוק https://www.example.com/api/callback ומשתני הסביבה GITHUB_CLIENT_ID ו-GITHUB_CLIENT_SECRET ב-Cloudflare נכונים
  • דף הבית עובד אבל דפי המאמרים מחזירים 404
    → ודא שה-baseURL ב-hugo.toml תואם לדומיין בפועל. בדוק ביומני הבנייה של Cloudflare ש-public/ נוצרה
  • /admin מציג עמוד ריק
    → ודא שסקריפטי Decap CMS שמוזכרים ב-static/admin/index.html אינם נחסמים על ידי תוספים או CSP; השבת תוספים וטעון מחדש

6. רשימת “החלף כאן” (תקציר)

קובץ / הגדרה מפתח ערך לדוגמה להגדרה
hugo.toml baseURL https://www.example.com
static/admin/config.yml repo <YOUR_GH_USERNAME>/my-hugo-blog
static/admin/config.yml branch התאם לסניף ברירת המחדל (main/master)
static/admin/config.yml base_url https://www.example.com
Cloudflare Pages HUGO_VERSION לדוגמה 0.128.0
Cloudflare Pages GITHUB_CLIENT_ID מזהה לקוח של אפליקציית GitHub OAuth
Cloudflare Pages GITHUB_CLIENT_SECRET סוד הלקוח של אפליקציית GitHub OAuth
GitHub OAuth App Callback URL https://www.example.com/api/callback
Cloudflare Pages Custom domain הוסף את www.example.com

7. נספח: קבצים לדוגמה (החלף את החלקים המסומנים)

7-1. functions/api/auth.js

// Cloudflare Pages Functions (/api/auth)
export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  try {
    const url = new URL(request.url);
    const redirectUrl = new URL('https://github.com/login/oauth/authorize');
    redirectUrl.searchParams.set('client_id', client_id);
    redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
    redirectUrl.searchParams.set('scope', 'repo user');
    redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
    return Response.redirect(redirectUrl.href, 302);
  } catch (err) {
    return new Response(String(err?.message || err), { status: 500 });
  }
}

7-2. functions/api/callback.js

function renderBody(status, content) {
  const html = `
  <script>
    const receiveMessage = (message) => {
      window.opener.postMessage(
        'authorization:github:${status}:${JSON.stringify(content)}',
        message.origin
      );
      window.removeEventListener("message", receiveMessage, false);
    }
    window.addEventListener("message", receiveMessage, false);
    window.opener.postMessage("authorizing:github", "*");
  </script>`;
  return new Blob([html]);
}

export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  const client_secret = env.GITHUB_CLIENT_SECRET;
  try {
    const url = new URL(request.url);
    const code = url.searchParams.get('code');
    const response = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
      body: JSON.stringify({ client_id, client_secret, code }),
    });
    const result = await response.json();
    if (result.error) {
      return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
    }
    const token = result.access_token;
    const provider = 'github';
    return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
  } catch (error) {
    return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
  }
}

7-3. static/admin/config.yml

backend:
  name: github
  repo: <YOUR_GH_USERNAME>/my-hugo-blog
  branch: main
  base_url: https://www.example.com
  auth_endpoint: /api/auth

media_folder: static/uploads
public_folder: /uploads

collections:
  - name: 'blog'
    label: 'Blog'
    folder: 'content/blog'
    create: true
    slug: '{{slug}}'
    editor:
      preview: false
    fields:
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Publish Date', name: 'date', widget: 'datetime' }
      - { label: 'Description', name: 'description', widget: 'string' }
      - { label: 'Body', name: 'body', widget: 'markdown' }

9. טיפים ל-SEO ולביצועים

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

  1. Sitemaps ו-robots.txt

    • שלח את public/sitemap.xml שנוצר אוטומטית ב-Hugo אל Google Search Console
    • התאם את התנהגות הזחלנים עם static/robots.txt
  2. OGP (Open Graph Protocol) וכרטיסי טוויטר

    • הוסף תגיות כמו <meta property="og:title"> ו-<meta property="og:image"> ל-layouts/_default/baseof.html
    • שפר את התצוגה של שיתופים ברשתות חברתיות
  3. המרת תמונות ל-WebP

    • השתמש ב-resources/_gen/images/ וב-Hugo Pipes כדי לאוטומט המרה ל-WebP
    • האץ את טעינת הדפים
  4. הוספת קטגוריות ותגים

    • הוסף categories ו-tags לפרונט-מאטר תחת content/blog/ והצג אותם בתבניות, למשל כענן תגים
  5. שימוש בנתונים מובְנים

    • ספק מטא-נתונים של המאמר בפורמט JSON-LD כדי להגדיל את הסיכוי לתוצאות עשירות

8. סיכום

  • צור ריפוזיטורי ב-GitHub, חבר אותו ל-Cloudflare Pages, הגדר OAuth של GitHub עבור Decap CMS ומפה את הדומיין המותאם — זה מביא אותך לקו הסיום
  • הכותבים מפרסמים דרך /admin/ → GitHub מבצע קומיטים → Cloudflare מפריסה אוטומטית
  • שפר תבניות ועיצובים מקומית עם hugo server, ואז דחוף דרך Git

זה כל מה שנדרש כדי להפעיל בלוג חסר-שרת שמבוסס על Hugo, GitHub, Decap CMS ו-Cloudflare.

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