Розробка веб-сайтів сьогодні включає такі терміни, як SSG (Static Site Generation - Генерація статичних сайтів), SSR (Server-Side Rendering - Рендеринг на стороні сервера), CSR (Client-Side Rendering - Рендеринг на стороні клієнта), ISR (Incremental Static Regeneration - Інкрементальна статична регенерація), Server Components (Компоненти сервера) та Client Components (Компоненти клієнта). Деякі з них є специфічними для React/Next.js, а інші є загальними техніками для вебу. Метою цього посту є пояснення цих концепцій шляхом визначення кожного терміну окремо та дослідження того, як вони взаємопов’язані.
CSR:
Рендеринг на стороні клієнта (Client-Side Rendering, CSR) передбачає рендеринг та генерацію контенту веб-сторінки безпосередньо в браузері клієнта за допомогою JavaScript. У цьому підході сервер зазвичай надає майже порожній HTML-документ, що містить лише один порожній <div>
елемент, часто позначений ID.
JavaScript потім використовує цей ID для заповнення елемента фактичним контентом після його підготовки, що може включати виклики API для отримання необхідних даних для сторінки. Хоча CSR можна реалізувати за допомогою звичайного JavaScript, фреймворки та бібліотеки, такі як React, Vue і Svelte, значно спрощують написання інтерактивного клієнтського коду.
Будь-який додаток, побудований з використанням згаданих вище технологій, без використання їхніх можливостей рендерингу на стороні сервера, використовує CSR.
``` ## SSR: Рендеринг на стороні сервера (Server Side Rendering) рендерить контент веб-сторінки на сервері.
Більш конкретно, цей термін використовується для **клієнтських фреймворків** (client-side frameworks) таких як React, Preact, Vue, Svelte тощо, щоб позначити їх здатність рендерити HTML з їхнього коду додатку (наприклад, компонент React) на сервері замість клієнта. Цей HTML контент потім може бути надісланий назад як відповідь браузеру, який рендерить його на екрані.
Таким чином, замість того, щоб отримувати порожній HTML документ з одним єдиним порожнім div, який служить точкою входу, як це відбувається в **CSR**, браузер отримує повністю згенерований HTML документ, який не потребує завантаження та виконання JavaScript бандла перед відображенням чого-небудь на екрані. Це пришвидшує **часи початкового завантаження** сторінок і корисне для SEO, оскільки деякі **павуки** (crawlers) не виконують JavaScript.
У своєму справжньому сенсі, в SSR, ми рендеримо HTML-дані для сторінки на сервері, захоплюючи початковий стан сторінки. Якщо ми говоримо про React, то ця сторінка буде просто компонентом.
Це не має ніяких особливостей порівняно з **компонентом, що рендериться на стороні клієнта** (client-side rendered component). Потім цей HTML контент разом з необхідним JavaScript бандлом для сторінки буде надісланий клієнту. Спочатку клієнт рендерить HTML, щоб користувач міг відразу побачити щось на екрані, поки браузер завантажує та парсить JavaScript бандл. JavaScript бандл включає React, який тепер бере на себе контроль над DOM та робить статичний HTML, що був спочатку відрендерений, знову інтерактивним, а також керує всіма клієнтськими маніпуляціями, як це відбувається в CSR.
Цей процес, коли React бере під контроль DOM, називається **_гідратацією_**.
// server.js
import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { App } from './App.js'
const server = express()
// Обробка вхідних запитів
server.get('/', (req, res) => {
// Рендеринг компонента React у рядок
const initialHtml = renderToString()
// Надсилання повного HTML з гідратаційним скриптом
res.send(`
${initialHtml}
) }) // Обслуговування клієнтського бандла server.use(express.static('public')) server.listen(3000, () => { console.log('Сервер працює на http://localhost:3000') })
// App.js export const App = () => { // Цей стан буде ініціалізовано на клієнті після гідратації const [count, setCount] = useState(0) return (
Counter: {count}
setCount(count + 1)}> Збільшити setCount(count - 1)}> Зменшити
) }// client.js import React from 'react' import { hydrateRoot } from 'react-dom/client' import { App } from './App.js' // Гідратація серверного контенту hydrateRoot( document.getElementById('root'), )
// package.json { "name": "react-ssr-example", "type": "module", "scripts": { "start": "node server.js" }, "dependencies": { "express": "^4.18.2", "react": "^18.2.0", "react-dom": "^18.2.0" } }
`` Цей приклад відображає підхід CSR, але використовує Рендеринг на стороні сервера (SSR) з Express як фреймворком для бекенду.
Використовуючи Next.js app router, ми можемо досягти такої ж функціональності, при цьому позбавляючись необхідності в шаблонному коді, як показано нижче:
// app/page.tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Next.js SSR Лічильник
Count: {count}
setCount(count + 1)}> Збільшити setCount(count - 1)}> Зменшити
) } ``` Ми обговоримо директиву _“use client”_ в розділі **Client Components** (Компоненти клієнта).
## Server Components:
Компоненти сервера (Server Components) — це компоненти, що рендеряться виключно на сервері, з їхнім JavaScript, який не пакується та не надсилається на клієнт. У результаті компоненти сервера є не інтерактивними.
Однак ця обмеженість надає їм унікальну перевагу: вони можуть безпосередньо звертатися до серверної функціональності, такої як **запити до бази даних** або взаємодія з третіми сторонами, прямо в самому компоненті.
// app/page.tsx
import { getUsers } from '../lib/db';
export default async function UsersPage() {
const users = await getUsers();
return (
Користувачі
{users.map(user => (
{user.name}
{user.email}
))}
); } ``` Тепер, як це порівнюється з SSR? Компонент сервера використовує SSR, оскільки він рендериться на сервері, з обмеженням, що він рендериться лише на сервері.
Для компонентів сервера не потрібно відправляти JavaScript.
Компоненти клієнта:
Компоненти клієнта дозволяють створювати звичайні інтерактивні React компоненти для клієнтської сторони, даючи змогу використовувати хуки життєвого циклу React, браузерні та DOM API і багато іншого. React компоненти, що використовуються в CSR та SSR, є прикладами компонентів клієнта.
Цікаво, що компоненти клієнта також (попередньо)рендеряться на сервері. Це відповідає класичному SSR, де компонент спочатку рендериться на сервері для генерації HTML для початкового вмісту сторінки. Потім сервер збирає необхідний JavaScript для активації інтерактивності та надсилає його до браузера.
У контексті Next.js компоненти в директорії pages можна розглядати як компоненти клієнта, які рендеряться на сервері (SSR). Однак можна було вибрати варіант без SSR для певних компонентів, імпортувавши їх через next/dynamic
.
З використанням app router позначення компонента як компонента клієнта вимагає директиви "use client" на початку файлу.
SSG:
Генерація статичних сайтів (Static Site Generation) означає створення однієї або кількох сторінок, які можуть або не можуть залежати від зовнішніх даних під час збірки, та їх повторне використання при нових запитах.
// app/about.tsx
export default function AboutPage() {
return (
Про компанію GreenTech Solutions
GreenTech Solutions, заснована в 2010 році, є лідером у галузі сталих технологій.
Ми створюємо екологічно чисті продукти, які допомагають захищати нашу планету.
Наша місія
Бути піонерами сталих технологій, що сприяють позитивним змінам у навколишньому середовищі.
Наша команда
Ми є різноманітною командою з понад 500 співробітників по всьому світу, серед яких інженери,
дизайнери та екологічні вчені.
Назад на головну ) } ``` Оскільки вміст цієї сторінки є **статичним**, HTML для неї може бути згенерований під час збірки та **повторно використаний** під час нових запитів без необхідності повторного рендерингу на момент запиту. Це підходить для таких випадків, як продукти, блоги тощо.
Ми навіть можемо використовувати SSG для сторінок, які потребують зовнішніх даних для вмісту та шляхів сторінок (slugs).
// app/products/[id]/page.tsx
import { getAllProductIds, getProductById } from '@/lib/db'
export async function generateStaticParams() {
const productIds = await getAllProductIds()
return productIds.map(id => ({ id }))
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProductById(params.id)
return (
{product.name}
{product.description}
) } ``` Тепер Next.js отримає всі ідентифікатори продуктів та деталі продукту в ProductPage і згенерує всі ці сторінки під час збірки та надасть їх статично. У двох наведених прикладах ми використовуємо виключно серверні компоненти для генерації статичних сайтів, але також можемо використовувати клієнтські компоненти.
Різниця полягає в тому, що замість просто створення HTML-пейлоаду під час збірки, ми також генеруємо JS пакет та використовуємо його повторно при кожному запиті.
ISR:
Інкрементальна статична регенерація (Incremental Static Regeneration)— довге й важке для вимови — дозволяє оновлювати статичний вміст без необхідності перебудови всього сайту, що робить можливим обробку великих обсягів статичного вмісту без значного збільшення часу збірки. Хоча наш попередній приклад SSG для генерації сторінок продуктів працює, він занадто спрощений.
Ось кілька проблем:
- Масштабованість: У реальних додатках часто є тисячі або навіть мільйони продуктів, що робить непрактичним генерувати статичні сторінки для всіх з них під час процесу збірки.
- Оновлення динамічного вмісту: Приклад припускає, що статичний вміст ніколи не змінюється, що рідко є правдою у реальних сценаріях.
Ось як ISR допомагає подолати проблеми з масштабованістю:
Замість того, щоб генерувати статичні сторінки для всіх продуктів, ми генеруємо їх лише для кількох сторінок.
Для інших сторінок ми налаштовуємо Next.js таким чином, щоб він обробляв сторінки, які не кешуються під час запиту, генерував сторінку, відправляв її назад і зберігав у кеші для майбутніх запитів.
// app/products/[id]/page.tsx
import { getImportantProductIds, getProductById } from '@/lib/db'
export async function generateStaticParams() {
const productIds = await getImportantProductIds()
return productIds.map(id => ({ id }))
}
// Ми попередньо рендеримо лише параметри з `generateStaticParams` під час збірки.
// Якщо запит надійде для шляху, який не був згенерований,
// Next.js буде рендерити сторінку за запитом.
export const dynamicParams = true // або false, щоб повернути 404 на невідомі шляхи
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProductById(params.id)
return (
{product.name}
{product.description}
) } ``` Для **Оновлень динамічного контенту**: Ми встановлюємо період перевірки для сторінки, щоб вона перевіряла та оновлювала контент після певного часу. ``` // app/products/[id]/page.tsx
import { getImportantProductIds, getProductById } from '@/lib/db'
export async function generateStaticParams() {
const productIds = await getImportantProductIds()
return productIds.map(id => ({ id }))
}
// Next.js інвалідовуватиме кеш, коли
// надійде запит, не частіше ніж раз на 60 секунд.
export const revalidate = 60
// Ми попередньо рендеримо лише параметри з `generateStaticParams` під час збірки.
// Якщо надійде запит на шлях, який не був згенерований,
// Next.js динамічно згенерує сторінку на сервері, поверне її клієнту
// та кешуватиме для майбутніх запитів.
export const dynamicParams = true // або false, щоб повернути 404 на невідомі шляхи
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProductById(params.id)
return (
{product.name}
{product.description}
) } ``` Тепер, загалом, ось як працює ця сторінка:
- Під час процесу `next build`, сторінки, що повертаються з `generateStaticParams`, генеруються та кешуються.
- Якщо сторінка, яка не була згенерована під час збірки, запитується, вона динамічно генерується на сервері, повертається клієнту і кешується для майбутніх запитів.
- Протягом 60 секунд після кешування сторінки, подальші запити отримуватимуть кешовану версію.
- Перший запит після 60-секундного періоду кешування все ще поверне кешований (**тепер застарілий**) контент.
Однак, у фоновому режимі сторінка **перегенерується** і кеш оновлюється з новою версією.
- Подальші запити до цієї сторінки отримуватимуть оновлений контент.
І це все для цього прикладу!
Перекладено з: [Evolution of Rendering in Next.js: Comparing CSR, SSR, Client/Server Components, SSG, ISR](https://medium.com/@talat.shehroze/evolution-of-rendering-in-next-js-comparing-csr-ssr-client-server-components-ssg-isr-e73d3cf8a1f8?source=rss------javascript-5)