\n```\n\n## 4. Progressive Enhancement — показать каркас, заполнить асинхронно\n\n```html\n\n\n\n \n\n\n \n
\n
\n
\n \n \n\n\n```\n\nПользователь видит каркас за 50мс, вместо ожидания 2 секунд.\n\n## 5. Web Workers — неблокирующие вычисления\n\nТяжёлые вычисления не блокируют рендеринг:\n\n```javascript\n// main.js\nconst worker = new Worker('worker.js');\n\n// Отправляем задачу в worker\nworker.postMessage({ data: largeArray });\n\n// Страница продолжает рендериться\nconsole.log('Страница рендерится');\n\nworker.onmessage = (e) => {\n const result = e.data;\n console.log('Результат вычисления:', result);\n // Обновляем DOM\n document.getElementById('result').textContent = result;\n};\n```\n\n```javascript\n// worker.js\nself.onmessage = (e) => {\n const data = e.data;\n \n // Тяжёлые вычисления в отдельном потоке\n let result = 0;\n for (let i = 0; i < 1000000000; i++) {\n result += i;\n }\n \n // Отправляем результат обратно\n self.postMessage({ result });\n};\n```\n\n## 6. Кеширование и CDN\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import FileResponse\nfrom fastapi_cache2 import FastAPICache\nfrom fastapi_cache2.backends.redis import RedisBackend\n\napp = FastAPI()\n\n@app.get(\"/page\")\n@cached(expire=3600) # Кешируем на 1 час\nasync def get_page():\n # Генерируем страницу один раз\n # Всем остальным пользователям отправляем из кеша (мгновенно)\n return render_template(\"page.html\")\n```\n\n## 7. Next.js / React — Concurrent Rendering\n\nReact 18+ не блокирует рендеринг:\n\n```jsx\nimport { Suspense } from 'react';\n\nfunction Page() {\n return (\n <>\n {/* Заголовок рендерится сразу */}\n
\n \n {/* Контент загружается асинхронно */}\n Загружаю...
}>\n \n \n \n {/* Сайдбар не ждёт SlowContent */}\n \n \n );\n}\n```\n\n## 8. HTMX — динамический контент без JavaScript\n\n```html\n
\n

Загружаю комментарии...

\n
\n```\n\nКогда div становится видимым в viewport — HTMX автоматически загружает контент.\n\n## Сравнение подходов\n\n| Метод | Скорость | Сложность | Лучше для |\n|-------|----------|-----------|----------|\n| SSE/Streaming | Быстро | Средняя | Большие страницы с долгой генерацией |\n| Lazy Loading | Очень быстро | Простая | Бесконечный скролл, галереи |\n| Progressive Enhancement | Мгновенно | Простая | UX — каркас показывается сразу |\n| Web Workers | Очень быстро | Средняя | CPU-интенсивные операции |\n| Кеширование | Мгновенно | Простая | Статический или редко меняющийся контент |\n| Concurrent Rendering | Быстро | Средняя | React приложения |\n\n## Практическое решение для FastAPI\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import StreamingResponse\nimport asyncio\n\napp = FastAPI()\n\n@app.get(\"/page\")\nasync def get_page():\n async def generate_page():\n # 1. Отправляем заголовок (fast paint)\n yield \"\\n

Мой сайт

\\n\"\n \n # 2. Загружаем основной контент из БД\n content = await fetch_main_content()\n yield f\"
{content}
\\n\"\n \n # 3. Загружаем сайдбар (может быть медленнее)\n sidebar = await fetch_sidebar()\n yield f\"\\n\"\n \n yield \"\"\n \n return StreamingResponse(generate_page(), media_type=\"text/html\")\n```\n\n## Итог\n\nНе ждать рендеринга можно несколькими способами:\n1. **Streaming** — отправляем HTML по частям\n2. **Lazy Loading** — загружаем контент при скролле\n3. **Progressive Enhancement** — показываем каркас сразу\n4. **Web Workers** — вычисления в отдельном потоке\n5. **Кеширование** — отправляем из кеша мгновенно\n6. **Concurrent Rendering** — React 18 не блокирует\n\nВыбирайте в зависимости от типа контента и требований к UX.","dateCreated":"2026-03-22T20:32:06.711935","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Как не ждать рендеринга страницы?

2.2 Middle🔥 201 комментариев
#Архитектура и паттерны#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Не ждать рендеринга страницы в веб-приложениях

Это вопрос о асинхронной обработке на фронтенде и бэкенде. Я расскажу несколько подходов.

1. Server-Sent Events (SSE) — поток данных от сервера

Сервер отправляет данные частями, не ждав полного рендеринга:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json

app = FastAPI()

@app.get("/api/render-stream")
async def render_stream():
    async def event_generator():
        # Отправляем заголовок страницы
        yield '{"type": "header", "content": "<header>Мой сайт</header>"}
        await asyncio.sleep(0.5)
        
        # Отправляем основной контент
        yield '{"type": "content", "content": "<main>Основной контент</main>"}
        await asyncio.sleep(1)
        
        # Отправляем сайдбар
        yield '{"type": "sidebar", "content": "<aside>Боковая панель</aside>"}
        await asyncio.sleep(0.3)
        
        # Отправляем футер
        yield '{"type": "footer", "content": "<footer>Подвал</footer>"}
    
    return StreamingResponse(
        event_generator(),
        media_type="application/x-ndjson"  # новая строка = новый JSON
    )

На фронтенде:

const eventSource = new EventSource('/api/render-stream');

function renderPart(type, content) {
    const element = document.querySelector(`[data-section="${type}"]`);
    if (element) {
        element.innerHTML = content;
    }
}

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    renderPart(data.type, data.content);
};

Результат: пользователь видит части страницы по мере их готовности, не ждёт полного рендеринга.

2. Streaming HTML (фронтенд отрисовывает по частям)

Бэкенд отправляет HTML фрагменты, фронтенд вставляет их на лету:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

@app.get("/page")
async def get_page():
    async def generate():
        yield "<html><head><title>Моя страница</title></head><body>\n"
        
        # Заголовок
        yield "<header>Добро пожаловать</header>\n"
        
        # Медленный контент (например, запрос к БД)
        await asyncio.sleep(2)
        yield "<main>\n"
        yield "  <h1>Основной контент</h1>\n"
        yield "  <p>Контент после 2 секунд ожидания</p>\n"
        yield "</main>\n"
        
        # Сайдбар
        await asyncio.sleep(1)
        yield "<aside>\n"
        yield "  <h2>Рекомендации</h2>\n"
        yield "  <ul><li>Товар 1</li></ul>\n"
        yield "</aside>\n"
        
        yield "</body></html>"
    
    return StreamingResponse(generate(), media_type="text/html")

В браузере: HTML парсится и отрисовывается по мере получения, не дожидаясь конца потока.

3. Lazy Loading — загружать данные по запросу

<div id="content">
    <h1>Моя страница</h1>
    
    <!-- Этот контент загружается только при скролле -->
    <div class="lazy-section" data-section="comments">
        <p>Загружаю комментарии...</p>
    </div>
</div>

<script>
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const section = entry.target.dataset.section;
            
            // Загружаем контент асинхронно
            fetch(`/api/section/${section}`)
                .then(res => res.text())
                .then(html => {
                    entry.target.innerHTML = html;
                });
            
            observer.unobserve(entry.target);
        }
    });
});

document.querySelectorAll('.lazy-section').forEach(el => {
    observer.observe(el);
});
</script>

4. Progressive Enhancement — показать каркас, заполнить асинхронно

<!DOCTYPE html>
<html>
<head>
    <style>
        .skeleton {
            background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
            background-size: 200% 100%;
            animation: loading 1.5s infinite;
        }
        @keyframes loading {
            0% { background-position: 200% 0; }
            100% { background-position: -200% 0; }
        }
    </style>
</head>
<body>
    <!-- Показываем скелет/каркас (fast paint) -->
    <div id="header" class="skeleton" style="height: 50px;"></div>
    <div id="content" class="skeleton" style="height: 300px;"></div>
    <div id="sidebar" class="skeleton" style="height: 200px;"></div>
    
    <script>
        // Загружаем реальный контент асинхронно
        Promise.all([
            fetch('/api/header').then(r => r.text()),
            fetch('/api/content').then(r => r.text()),
            fetch('/api/sidebar').then(r => r.text())
        ]).then(([header, content, sidebar]) => {
            document.getElementById('header').innerHTML = header;
            document.getElementById('content').innerHTML = content;
            document.getElementById('sidebar').innerHTML = sidebar;
        });
    </script>
</body>
</html>

Пользователь видит каркас за 50мс, вместо ожидания 2 секунд.

5. Web Workers — неблокирующие вычисления

Тяжёлые вычисления не блокируют рендеринг:

// main.js
const worker = new Worker('worker.js');

// Отправляем задачу в worker
worker.postMessage({ data: largeArray });

// Страница продолжает рендериться
console.log('Страница рендерится');

worker.onmessage = (e) => {
    const result = e.data;
    console.log('Результат вычисления:', result);
    // Обновляем DOM
    document.getElementById('result').textContent = result;
};
// worker.js
self.onmessage = (e) => {
    const data = e.data;
    
    // Тяжёлые вычисления в отдельном потоке
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
        result += i;
    }
    
    // Отправляем результат обратно
    self.postMessage({ result });
};

6. Кеширование и CDN

from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi_cache2 import FastAPICache
from fastapi_cache2.backends.redis import RedisBackend

app = FastAPI()

@app.get("/page")
@cached(expire=3600)  # Кешируем на 1 час
async def get_page():
    # Генерируем страницу один раз
    # Всем остальным пользователям отправляем из кеша (мгновенно)
    return render_template("page.html")

7. Next.js / React — Concurrent Rendering

React 18+ не блокирует рендеринг:

import { Suspense } from 'react';

function Page() {
    return (
        <>
            {/* Заголовок рендерится сразу */}
            <Header />
            
            {/* Контент загружается асинхронно */}
            <Suspense fallback={<div>Загружаю...</div>}>
                <SlowContent />
            </Suspense>
            
            {/* Сайдбар не ждёт SlowContent */}
            <Sidebar />
        </>
    );
}

8. HTMX — динамический контент без JavaScript

<div hx-get="/api/comments" hx-trigger="revealed">
    <p>Загружаю комментарии...</p>
</div>

Когда div становится видимым в viewport — HTMX автоматически загружает контент.

Сравнение подходов

МетодСкоростьСложностьЛучше для
SSE/StreamingБыстроСредняяБольшие страницы с долгой генерацией
Lazy LoadingОчень быстроПростаяБесконечный скролл, галереи
Progressive EnhancementМгновенноПростаяUX — каркас показывается сразу
Web WorkersОчень быстроСредняяCPU-интенсивные операции
КешированиеМгновенноПростаяСтатический или редко меняющийся контент
Concurrent RenderingБыстроСредняяReact приложения

Практическое решение для FastAPI

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

@app.get("/page")
async def get_page():
    async def generate_page():
        # 1. Отправляем заголовок (fast paint)
        yield "<html><body>\n<h1>Мой сайт</h1>\n"
        
        # 2. Загружаем основной контент из БД
        content = await fetch_main_content()
        yield f"<main>{content}</main>\n"
        
        # 3. Загружаем сайдбар (может быть медленнее)
        sidebar = await fetch_sidebar()
        yield f"<aside>{sidebar}</aside>\n"
        
        yield "</body></html>"
    
    return StreamingResponse(generate_page(), media_type="text/html")

Итог

Не ждать рендеринга можно несколькими способами:

  1. Streaming — отправляем HTML по частям
  2. Lazy Loading — загружаем контент при скролле
  3. Progressive Enhancement — показываем каркас сразу
  4. Web Workers — вычисления в отдельном потоке
  5. Кеширование — отправляем из кеша мгновенно
  6. Concurrent Rendering — React 18 не блокирует

Выбирайте в зависимости от типа контента и требований к UX.

Как не ждать рендеринга страницы? | PrepBro