Въведение в Rendering стратегиите

Разбирането на различните rendering стратегии е критично за всеки frontend разработчик. В тази статия ще разгледаме детайлно как работят Server-Side Rendering (SSR), Static Site Generation (SSG) и Incremental Static Regeneration (ISR), с фокус върху това как Go backend-ът обработва заявките във всеки случай.

Web Architecture

Архитектура на нашата система

Преди да разгледаме различните стратегии, нека видим как е структурирана нашата система:

┌──────────────────────────────────────────────────────────────────────────┐
│                           АРХИТЕКТУРА                                     │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│    ┌─────────┐         ┌─────────┐         ┌──────────┐                 │
│    │ Browser │ ──────► │  Nginx  │ ──────► │  Astro   │                 │
│    │ Client  │ ◄────── │ (Proxy) │ ◄────── │  (SSR)   │                 │
│    └─────────┘         └─────────┘         └────┬─────┘                 │
│                              │                   │                       │
│                              │                   │ API calls             │
│                              │                   ▼                       │
│                              │            ┌──────────┐                   │
│                              └──────────► │    Go    │                   │
│                                /api/*     │ Backend  │                   │
│                                           └────┬─────┘                   │
│                                                │                         │
│                                                ▼                         │
│                                          ┌──────────┐                    │
│                                          │  SQLite  │                    │
│                                          │    DB    │                    │
│                                          └──────────┘                    │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Server-Side Rendering (SSR)

При SSR, HTML-ът се генерира на сървъра при всяка заявка. Това означава, че съдържанието винаги е актуално.

Процес на SSR заявка

┌─────────────────────────────────────────────────────────────────────────────┐
│                        SSR REQUEST FLOW                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. Browser         2. Nginx           3. Astro          4. Go Backend     │
│     │                  │                  │                  │              │
│     │ GET /article/5   │                  │                  │              │
│     │─────────────────►│                  │                  │              │
│     │                  │ proxy_pass       │                  │              │
│     │                  │─────────────────►│                  │              │
│     │                  │                  │ fetch API        │              │
│     │                  │                  │─────────────────►│              │
│     │                  │                  │                  │ Query DB     │
│     │                  │                  │                  │──────┐       │
│     │                  │                  │                  │      │       │
│     │                  │                  │                  │◄─────┘       │
│     │                  │                  │    JSON data     │              │
│     │                  │                  │◄─────────────────│              │
│     │                  │   Render HTML    │                  │              │
│     │                  │◄─────────────────│                  │              │
│     │   Full HTML      │                  │                  │              │
│     │◄─────────────────│                  │                  │              │
│     │                  │                  │                  │              │
│     ▼                  ▼                  ▼                  ▼              │
│   ~50-200ms        ~1ms              ~10-50ms           ~5-20ms            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Go Backend код за SSR заявка

// Handler за вземане на статия
func getArticleHandler(w http.ResponseWriter, r *http.Request) {
    // Взимаме ID от query параметрите
    idStr := r.URL.Query().Get("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        jsonResponse(w, map[string]string{"error": "Invalid ID"}, 400)
        return
    }
    
    // Query към SQLite базата
    var article Article
    err = db.QueryRow(`
        SELECT id, title, content, author, published, render_mode, 
               created_at, updated_at 
        FROM articles 
        WHERE id = ? AND published = true
    `, id).Scan(
        &article.ID, &article.Title, &article.Content, 
        &article.Author, &article.Published, &article.RenderMode,
        &article.CreatedAt, &article.UpdatedAt,
    )
    
    if err == sql.ErrNoRows {
        jsonResponse(w, map[string]string{"error": "Not found"}, 404)
        return
    }
    
    // Връщаме JSON отговор
    jsonResponse(w, article, 200)
}

Astro SSR страница

---
// Този код се изпълнява на сървъра при ВСЯКА заявка
import Layout from '../layouts/Layout.astro';
import { getArticle } from '../lib/api';

const { id } = Astro.params;
const article = await getArticle(parseInt(id));
const timestamp = new Date().toISOString();
---

<Layout title={article.title}>
  <article>
    <h1>{article.title}</h1>
    <p>Генерирано на: {timestamp}</p>
    <p>Render mode: {article.render_mode}</p>
    <div set:html={article.content} />
  </article>
</Layout>

Кога да използвате SSR

  • Персонализирано съдържание (user dashboard)
  • Реално-времеви данни
  • Динамични цени или наличности
  • Страници с чести актуализации

Static Site Generation (SSG)

При SSG, HTML-ът се генерира по време на build процеса. Резултатът е статичен файл, който се сервира директно.

Процес на SSG заявка

┌─────────────────────────────────────────────────────────────────────────────┐
│                        SSG REQUEST FLOW                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                        BUILD TIME (npm run build)                           │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│     Astro              Go Backend           File System                     │
│       │                    │                     │                          │
│       │ fetch all articles │                     │                          │
│       │───────────────────►│                     │                          │
│       │                    │ Query DB            │                          │
│       │   JSON articles    │                     │                          │
│       │◄───────────────────│                     │                          │
│       │                    │                     │                          │
│       │ Generate HTML for each article          │                          │
│       │─────────────────────────────────────────►│                          │
│       │                    │     /article/1.html │                          │
│       │                    │     /article/2.html │                          │
│       │                    │     /article/3.html │                          │
│                                                                             │
│                        RUNTIME (user request)                               │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  Browser              Nginx/CDN            Static Files                     │
│     │                    │                     │                            │
│     │ GET /article/1     │                     │                            │
│     │───────────────────►│                     │                            │
│     │                    │ Read file           │                            │
│     │                    │────────────────────►│                            │
│     │                    │    HTML content     │                            │
│     │                    │◄────────────────────│                            │
│     │   Full HTML        │                     │                            │
│     │◄───────────────────│                     │                            │
│     │                    │                     │                            │
│     ▼                    ▼                     ▼                            │
│   ~5-20ms            ~1-5ms              (pre-built)                       │
│                                                                             │
│   ⚡ Go Backend НЕ се вика при runtime!                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Важно: Контактната форма при SSG

Когато статията е SSG, страницата е статична, но контактната форма все още работи чрез JavaScript на клиента:

┌─────────────────────────────────────────────────────────────────────────────┐
│              SSG PAGE WITH CONTACT FORM                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   Static HTML Page            Browser JavaScript         Go Backend         │
│   (pre-rendered)                                                            │
│        │                           │                         │              │
│        │  Page loads instantly     │                         │              │
│        │◄──────────────────────────│                         │              │
│        │                           │                         │              │
│        │  User fills form          │                         │              │
│        │  and clicks Submit        │                         │              │
│        │                           │                         │              │
│        │  JavaScript intercepts    │                         │              │
│        │─────────────────────────►│                         │              │
│        │                           │ POST /api/inquiry       │              │
│        │                           │────────────────────────►│              │
│        │                           │                         │ Save to DB   │
│        │                           │     {success: true}     │              │
│        │                           │◄────────────────────────│              │
│        │  Show success message     │                         │              │
│        │◄──────────────────────────│                         │              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Client-side JavaScript за формата

// Този код се изпълнява в браузъра, не на сървъра
contactForm.addEventListener('submit', async (e) => {
  e.preventDefault();
  
  const inquiry = {
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
    source_page: window.location.pathname, // '/article/5'
    source_type: 'SSG', // Знаем че страницата е статична
    article_id: parseInt(articleId)
  };
  
  // Директна заявка към Go backend през Nginx
  const response = await fetch('/api/inquiry', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(inquiry)
  });
  
  if (response.ok) {
    showSuccessMessage();
  }
});

Astro SSG конфигурация

---
// export const prerender = true казва на Astro да генерира
// тази страница статично при build
export const prerender = true;

import Layout from '../layouts/Layout.astro';
import { getArticle } from '../lib/api';

const { id } = Astro.params;
const article = await getArticle(parseInt(id));
const buildTime = new Date().toISOString(); // Фиксирано при build!
---

<Layout title={article.title}>
  <article>
    <h1>{article.title}</h1>
    <p>Генерирано при build: {buildTime}</p>
    <!-- Този timestamp НИКОГА няма да се промени без rebuild -->
  </article>
  
  <!-- Контактната форма работи с client-side JS -->
  <ContactForm articleId={id} renderMode="SSG" client:load />
</Layout>

Incremental Static Regeneration (ISR)

ISR комбинира предимствата на SSG (бързина) и SSR (свежи данни). Страницата е статична, но се регенерира периодично.

ISR процес

┌─────────────────────────────────────────────────────────────────────────────┐
│                        ISR REQUEST FLOW                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                     First Request (cache empty)                             │
│  ─────────────────────────────────────────────────────────────────────────  │
│     │                                                                       │
│     │  Same as SSR - generate and cache                                    │
│     ▼                                                                       │
│                                                                             │
│                     Subsequent Requests (within TTL)                        │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  Browser              Cache                Go Backend                       │
│     │                   │                      │                            │
│     │ GET /article/1    │                      │                            │
│     │──────────────────►│                      │                            │
│     │                   │ Cache HIT            │                            │
│     │   Cached HTML     │  (no backend call)   │                            │
│     │◄──────────────────│                      │                            │
│                                                                             │
│                     After TTL expires (stale-while-revalidate)              │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                             │
│  Browser              Cache                Go Backend                       │
│     │                   │                      │                            │
│     │ GET /article/1    │                      │                            │
│     │──────────────────►│                      │                            │
│     │   Stale HTML      │                      │                            │
│     │◄──────────────────│                      │                            │
│     │                   │ Background refresh   │                            │
│     │                   │─────────────────────►│                            │
│     │                   │   Fresh data         │                            │
│     │                   │◄─────────────────────│                            │
│     │                   │ Update cache         │                            │
│                                                                             │
│   Next request gets fresh HTML                                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Astro ISR имплементация

// В Astro няма native ISR, но можем да го симулираме:
const CACHE_TTL = 30; // секунди
const cache = new Map();

async function getArticleWithCache(id: number) {
  const cacheKey = `article_${id}`;
  const cached = cache.get(cacheKey);
  
  if (cached && Date.now() - cached.timestamp < CACHE_TTL * 1000) {
    return cached.data;
  }
  
  // Fetch fresh data
  const article = await fetch(`${API_BASE}/api/article?id=${id}`)
    .then(r => r.json());
  
  cache.set(cacheKey, {
    data: article,
    timestamp: Date.now()
  });
  
  return article;
}

Сравнение на стратегиите

┌────────────────┬─────────────┬─────────────┬─────────────┬─────────────────┐
│   Критерий     │     SSR     │     SSG     │     ISR     │   Препоръка     │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ TTFB           │   Бавен     │   Много бърз│   Бърз      │ SSG за статични │
│                │  (50-200ms) │   (5-20ms)  │  (5-50ms)   │                 │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ Данни         │   Винаги    │   От build  │ Периодично  │ SSR за динам.   │
│ актуалност    │   свежи     │   момента   │ обновявани  │                 │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ Сървърно      │   Високо    │   Нулево    │   Ниско     │ SSG за трафик   │
│ натоварване   │             │             │             │                 │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ CDN кеширане  │   Трудно    │   Лесно     │   Лесно     │ SSG/ISR за CDN  │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ SEO           │   Отлично   │   Отлично   │   Отлично   │ Всички са добри │
├────────────────┼─────────────┼─────────────┼─────────────┼─────────────────┤
│ Персонализ.   │   Да        │   Не        │   Не        │ SSR за auth     │
└────────────────┴─────────────┴─────────────┴─────────────┴─────────────────┘

Автоматичен Rebuild при SSG

В нашата система, когато се промени SSG статия, Go backend-ът автоматично тригерира rebuild:

// triggerRebuild се вика когато SSG статия се промени
func triggerRebuild() error {
    log.Println("Triggering Astro rebuild...")
    
    // Изпълняваме npm run build
    cmd := exec.Command("bash", "-c", 
        "cd /var/www/go-astro-1.pasifora.com/frontend && npm run build")
    output, err := cmd.CombinedOutput()
    
    if err != nil {
        log.Printf("Rebuild error: %v\nOutput: %s", err, string(output))
        return err
    }
    
    log.Println("Rebuild completed successfully")
    
    // Рестартираме Node.js сървъра
    exec.Command("bash", "-c", 
        "pkill -f 'entry.mjs'").Run()
    
    go func() {
        time.Sleep(1 * time.Second)
        exec.Command("bash", "-c",
            "cd /var/www/.../frontend && "+
            "HOST=127.0.0.1 PORT=4322 "+
            "nohup node ./dist/server/entry.mjs &").Run()
    }()
    
    return nil
}

Заключение

Изборът между SSR, SSG и ISR зависи от конкретните нужди:

  • SSG: Блогове, документация, маркетингови страници
  • SSR: Dashboards, e-commerce с динамични цени, персонализирано съдържание
  • ISR: Новинарски сайтове, продуктови каталози с редки обновявания

Важното е да разбирате как работи всяка стратегия и как данните текат от браузъра през различните слоеве до базата данни и обратно.


Брой думи: 3,412