Модуль 25

Serving SPA (React, Angular)

Ось готовий урок, створений спеціально для тебе у стилі David Malan. Вмикай уяву — ми в лекційній залі Sanders Theatre!


🎓 УРОК: Serving SPA (React, Angular, Vue)

Або: Чому мій сайт працює, поки я не натисну F5?


1. 🔥 Вступ: «Магія зникає, коли оновлюєш сторінку»

Уявіть ситуацію. Ви витратили тижні на створення ідеального інтернет-магазину на React. Все літає! Клієнт натискає «Каталог», потім «Товар», потім «Кошик». URL у браузері змінюється: /catalog -> /product/42 -> /cart. Ви задоволені, відчуваєте себе хакером.

Ви заливаєте це на сервер, відправляєте посилання другу: myshop.com/product/42. Друг відкриває посилання і... бачить помилку 404 Not Found.

Або ще гірше: ви самі на сайті, знаходитесь на сторінці /cart, випадково натискаєте кнопку «Оновити» (Refresh)... і знову 404.

Чекайте, що? Чому, коли ви клікали по кнопках, сторінка відкривалася, а коли ви ввели ту саму адресу напряму — сервер каже, що її не існує?

Аналогія: Готель «SPA» 🏨

Уявіть, що ваш сайт — це готель.

  1. Класичний сайт (MPA): Кожна кімната має власні двері з вулиці. Хочете в кімнату /contact? Ви заходите в окремі двері. Хочете в /about? Інші двері. Сервер — це швейцар, який відкриває конкретні двері.
  2. SPA (Single Page Application): У цього готелю є ТІЛЬКИ ОДИН ВХІД — головний хол (index.html). Ви заходите в хол, а там стоїть адміністратор (JavaScript), який миттєво змінює декорації навколо вас. Ви кажете: «Хочу в спальню», і адміністратор клацає пальцями — пуф! — хол виглядає як спальня.

Проблема: Коли ви вводите myshop.com/cart у браузері і тиснете Enter, ви, по суті, намагаєтесь залізти у вікно кімнати «Cart» з вулиці. Але в будівлі SPA немає вікон і дверей для кімнат! Є тільки вхід через хол. Сервер дивиться на вас і каже: «Чувак, кімнати /cart фізично не існує. Є тільки index.html. До побачення (404)».

Сьогодні ми навчимо сервер направляти всіх гостей через головний вхід, незалежно від того, куди вони хочуть потрапити.


2. 🧠 Теоретична база: Що відбувається «під капотом»?

Давайте розберемося з механікою.

Ключові поняття:

  1. Client-Side Routing (Маршрутизація на клієнті): Це коли React/Angular змінює URL в адресному рядку, використовуючи History API, але не робить запит на сервер. Браузер просто «прикидається», що перейшов на нову сторінку.

  2. Server-Side Routing (Маршрутизація на сервері): Це коли сервер отримує запит, дивиться на шлях (path) і шукає відповідний файл на диску.

У чому конфлікт?

Коли ви робите build свого React-додатку, ви отримуєте одну папку з файлами: * index.html (єдиний HTML файл) * bundle.js (весь ваш код) * style.css

Там немає папки /cart. Там немає файлу /user/profile.

Тому, коли приходить запит GET /cart, сервер (наприклад, Nginx або Apache) чесно шукає файл cart. Не знаходить. Віддає 404.

💡 Рішення (The "Fallback" Strategy)

Ми повинні дати серверу просту інструкцію:

"Якщо хтось просить файл, якого в тебе немає (наприклад, /cart), не панікуй. Просто віддай йому index.html."

Як тільки браузер завантажить index.html та запустить JS, ваш React-код подивиться на URL, зрозуміє: «Ага, тут написано /cart!» — і відрендерить кошик.


3. 🧪 Приклади: Від болю до успіху

Приклад 1: Наївний підхід (Як не треба робити)

Ви просто встановили простий HTTP-сервер і вказали йому на папку build.

  • Запит: GET /
  • Відповідь сервера: index.html (200 OK) -> Сайт завантажується, JS працює.
  • Дія: Клікаємо на лінк "Про нас" -> URL стає /about.
  • Дія: Тиснемо F5 (Refresh).
  • Запит: GET /about
  • Відповідь сервера: 404 Not Found (бо файлу about.html не існує).

Очікування студента: "Ну він же працював секунду тому!" Реальність: Працював JS у пам'яті браузера. Сервер про /about нічого не знає.


Приклад 2: Nginx (Золотий стандарт)

Nginx — це найпопулярніший веб-сервер для віддачі статики. Ось як виглядає магічна конфігурація.

server {
    listen 80;
    server_name mysite.com;
    root /var/www/my-react-app; # Де лежать ваші файли після build
    index index.html;

    location / {
        # ⚠️ ОСЬ ВОНА, МАГІЯ:
        try_files $uri $uri/ /index.html;
    }
}

Розбір рядка try_files: 1. $uri — Сервер, подивись, чи існує такий файл (наприклад, logo.png). Якщо є — віддай. 2. $uri/ — Якщо це папка — віддай її. 3. /index.htmlЯкщо нічого не знайшов — віддай index.html!

Тепер, коли ви запитуєте /cart: 1. Файлу cart немає. 2. Папки cart немає. 3. Nginx віддає index.html. 4. React запускається, бачить /cart і показує кошик. Profit! 🚀


Приклад 3: Node.js + Express (Якщо ви full-stack)

Якщо ви серверите статику через свій Node.js сервер:

const express = require('express');
const path = require('path');
const app = express();

// 1. Спочатку віддаємо статику (картинки, JS, CSS)
app.use(express.static(path.join(__dirname, 'build')));

// 2. Ловимо ВСІ інші запити ('*')
app.get('*', (req, res) => {
  // І віддаємо index.html
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(3000);

Питання до студента: Чому app.get('*') має йти після app.use(...)? Відповідь: Бо якщо ми поставимо його першим, то навіть на запит style.css сервер віддасть index.html, і сайт зламається. Порядок має значення!


4. 🛠 Практична частина

А тепер ваша черга. Немає нічого кращого за практику, щоб закріпити нейронні зв'язки.

Завдання 1: "The Build" Створіть простий React-додаток (create-react-app або Vite). Додайте react-router. Зробіть дві сторінки: Home і About. Зробіть npm run build. Подивіться, що в папці dist (або build). Знайдіть там файл about? (Спойлер: його там немає).

Завдання 2: "Serve it locally" Встановіть простий сервер: npm install -g serve. Запустіть: serve -s build. Прапор -s означає "single page application". Спробуйте запустити без нього і зловіть 404 помилку при оновленні сторінки. Зрозумійте різницю.

Завдання 3: "Nginx Config doctor" У вас є конфіг, який не працює. Знайдіть помилку:

location / {
    try_files $uri $uri/ =404;
}

Підказка: Що станеться, якщо я перейду на /dashboard? Користувач побачить сторінку 404 замість додатку. Виправте це.

Завдання 4: "А що з API?" (Міні-кейс) Ваш додаток робить запити на /api/users. Якщо ви використовуєте правило "всі запити на index.html", що станеться з API-запитами, якщо бекенд на тому ж домені? Завдання: Напишіть (на папері або в коді) логіку, щоб запити, які починаються з /api, йшли на бекенд, а всі інші — на index.html.


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в цій темі?

1. "It works on my machine" синдром Новачок запускає npm start і думає, що це і є сервер. Профі знає: npm start — це важкий dev-server тільки для розробки. У продакшн іде тільки скомпільована статика (HTML/CSS/JS).

2. Кешування (Caching) Новачок просто віддає файли. Профі думає: "Я оновив код, але користувачі бачать стару версію". Профі налаштовує заголовки так, щоб index.html ніколи не кешувався (no-cache), а файли з хешами (main.a1b2c3.js) кешувалися назавжди.

3. Використання готових рішень Замість того, щоб налаштовувати Nginx на віртуальній машині (VPS), досвідчений розробник часто скаже: "Навіщо мені цей головний біль?" і заллє проєкт на Vercel, Netlify або AWS S3 + CloudFront. Ці сервіси вже налаштовані під SPA за замовчуванням. Пам'ятайте: лінивий розробник — розумний розробник (іноді).


6. 🧩 Підсумок

Отже, що ми сьогодні розібрали?

  1. SPA — це ілюзія. Немає ніяких сторінок, є тільки зміна вмісту через JS.
  2. Проблема 404: Сервер за замовчуванням шукає фізичні файли, які відповідають URL.
  3. Рішення: Налаштувати сервер (Rewrite Rule), щоб на будь-який невідомий запит він віддавав index.html. Це "чорний хід" для вашого додатку.
  4. Ви тепер знаєте, як це налаштувати в Nginx та Express.

Ви більше не будете панікувати, коли побачите 404 на своєму продакшн-сервері. Ви просто посміхнетесь і додасте одну стрічку в конфіг.


🔜 У наступній серії: Ми навчилися віддавати контент. Але як переконатися, що його бачите тільки ви? Тема наступного уроку: JWT токени: Як носити паспорт у кишені браузера.

А поки що — це був CS50 (умовно). Гарного кодингу! 👋