Перейти до вмісту

Фронтенд

та частина системи, що працює у браузері читача

Що це таке

Фронтенд — це частина програмної системи, яку люди бачать і якою керують: сторінки, екрани, форми й елементи керування разом із кодом, що їх малює й реагує на введення. Англійською його також пишуть front end або front-end, а у вебі ту саму територію часто називають клієнтською стороною, client side.

У вебі фронтенд складається з трьох мов, які розуміє браузер. HTML задає сторінці структуру й зміст, CSS визначає її вигляд, а JavaScript додає поведінку. Браузер — це те, що документ W3C Architecture of the World Wide Web називає user agent: програма, яка діє від імені людини, отримує дані із серверів і показує їх. Усе, що фронтенд надсилає браузеру, опиняється на пристрої, який належить комусь іншому.

Його партнер — бекенд, який зберігає дані, застосовує бізнес-правила й відповідає на запити. Вони спілкуються через HTTP, зазвичай за допомогою API. Межу між ними хочеться провести там, де проходить мережа, але сучасні фреймворки її розмивають. У Next.js сторінки й layouts за замовчуванням є Server Components: вони створюють те, що бачить користувач, і виконуються на сервері. Директива "use client" на початку файлу позначає межу, і цей файл, його імпорти та компоненти, які він рендерить, потрапляють у бандл, що вирушає до браузера. Фронтендом код робить його завдання, представлення й взаємодія, а місце виконання — окреме рішення.

Чому це важливо

Перше рішення стосується того, де відбувається робота. Архітектурний документ W3C описує спектр, на одному кінці якого все робить клієнт, а на іншому все робить сервер, і зазначає, що більшість рішень поєднує обидва підходи. Місце застосунку на цьому спектрі залежить, за словами документа, від потужності клієнта, потужності й навантаження сервера та пропускної здатності між ними. Документ називає компроміс з обох боків: підлаштування вмісту на сервері знижує ефективність кешування, а коли кількість можливих клієнтів необмежена, краще масштабується перенесення обчислень на них. Звичні варіанти такі: сторінки, зібрані один раз, сторінки, які сервер рендерить на кожен запит, сторінки, складені в браузері, і їхні поєднання. Сторінку, однакову для всіх, найдешевше зібрати один раз і кешувати. Сторінку з даними конкретного користувача десь рендерять на кожен запит, і якщо це браузер, кожен читач завантажує код, що виконує цю роботу.

Друге рішення стосується довіри, і робоча відповідь така: сервер вважає все, що надходить від фронтенду, неперевіреним. Усе, що доставляє фронтенд, людина за браузером може прочитати, змінити й надіслати повторно. Прихована кнопка чи форма, яка відмовляється відправлятися, — це ввічливість щодо чесного користувача й невеликий обхід для будь-кого іншого, тому кожен дозвіл і кожна перевірка, які мають значення, повторюються на сервері. Секрети підкоряються тому самому правилу: API-ключ, що потрапив у бандл, є публічним, хоч би як називалася змінна. Content Security Policy обмежує, що може зробити впроваджений скрипт, а її специфікація подає її як глибокий захист, який доповнює ретельну валідацію введення й кодування виводу, а не замінює їх.

Фронтенд — це також місце, де доступність виграють або програють. WCAG 2.2, рекомендація W3C від грудня 2024 року, встановлює критерії успіху, які можна перевірити, і багато з них залежать від рішень у фронтенді: чи можна дістатися до елемента керування й скористатися ним із клавіатури та чи повідомляє він допоміжним технологіям своє ім’я й роль. Нативні елементи HTML виконують кілька з цих критеріїв одразу, а власний віджет мусить відтворити кожен вручну.

Нарешті, фронтенд, який знає, як база даних зберігає дані, доводиться змінювати щоразу, коли змінюється база, а фронтенд, що обмежується представленням, дає кожній стороні рухатися у власному темпі.

Як це виглядає на практиці

Фронтенд на Next.js 16 обирає режим рендерингу для кожної частини застосунку. Маркетингові й публічні сторінки генеруються статично, а інтерактивні частини рендеряться в браузері. На автентифікованому дашборді й великих сторінках-каталогах, де патерн N+1 перетворював одну сторінку на каскад мережевих звернень, фабрика createPrefetchedServerPage отримує дані сторінки на сервері й передає їх клієнту вже заповненими, а стратегію інвалідації кешу задокументовано для кожного запиту. Сторінка, перенесена на фабрику, завантажується за одне звернення, і наступна перенесена сторінка успадковує цю поведінку. Там же сказано, що перехід триває: решта автентифікованого застосунку досі рендериться на клієнті й стоїть у черзі.

Багаторівнева морська платформа дає своєму фронтенду одне завдання й нічого більше. Фронтенд на Next.js відповідає за представлення й інтерактивність. Навмисно тонкий API на Go, побудований на Huma поверх Fiber, маршрутизує й валідує запити, застосовує фільтрацію безпеки та оркеструє, а бізнес-логіка живе у функціях PostgreSQL. Коли правило змінюється, воно змінюється в одному рівні, у SQL, а фронтенд не переймається, коли схема змінюється під ним. Межі зафіксували письмово й закріпили через код-рев’ю, і саме цьому розділенню сторінка приписує те, що продукт міг і далі нарощувати функціональність без гниття архітектури.

Робота з посилення захисту застосунку виходить із припущення, що клієнт ворожий. Усе, що браузерові дозволено виконати й надіслати, задає один файл, proxy.ts: мідлвар Next.js додає до кожної відповіді Content Security Policy зі свіжим nonce і strict-dynamic, заголовок HSTS та cookie з атрибутом SameSite. Сама межа проходить на сервері. Права доступу, тобто тариф, роль, організацію й прив’язку до судна, сервер повторно перевіряє на кожен запит, а фронтендні обмеження розглядаються лише як UX: вони визначають, що бачить людина, а сервер визначає, що їй дозволено робити.

Де виникають проблеми

Збої фронтенду зазвичай мають спільну причину: щось вирішили в браузері, хоча це належало серверу, або навпаки.

API-ключ з’являється в JavaScript, який може завантажити будь-який відвідувач. У Next.js змінна середовища потрапляє в бандл браузера, лише коли її назва починається з NEXT_PUBLIC_, а будь-яку іншу змінну, прочитану з клієнтського коду, замінює порожній рядок. Запит повертається неавторизованим, хтось швидко перейменовує змінну, щоб усе запрацювало, і секрет опиняється в публічному файлі. Код, якому потрібен секрет, варто тримати на сервері, а його модуль позначати пакетом server-only, щоб імпорт із Client Component зупиняв збирання. Кожен ключ, який уже потрапив у реліз, треба замінити.

import "server-only";
export async function getInvoices(customerId: string) {
    const url = `${process.env.BILLING_URL}/customers/${customerId}/invoices`;
    const headers = { Authorization: `Bearer ${process.env.BILLING_KEY}` };
    const res = await fetch(url, { headers });
    return res.json();
}

Користувач дістається записів, які інтерфейс від нього приховав. Перевірка дозволів жила у фронтенді як route guard, прихований пункт меню або вимкнена кнопка. Будь-хто, хто читає мережевий трафік, може викликати API напряму з іншими параметрами. Кожне право варто перевіряти на сервері на кожен запит, щодо користувача, який увійшов, і конкретного запису, а перевірки у фронтенді вважати зручністю для чесних користувачів.

Сторінка заповнюється по одному спінеру за раз. Кожен компонент отримує власні дані після рендерингу, тож запит дочірнього компонента чекає на запит батьківського, і сторінка приходить водоспадом мережевих звернень. Дані сторінки варто отримувати разом і ближче до джерела, на сервері, де це дозволяє фреймворк, і передавати вниз.

Користувачі клавіатури й екранних читачів застрягають на саморобному елементі керування. Клікабельний div отримує поведінку кнопки для миші й більше нічого: клавіша Tab його оминає, Enter і пробіл нічого не роблять, а допоміжні технології не дізнаються, чим він є. WCAG 2.2 на рівні A вимагає, щоб уся функціональність працювала з клавіатури (критерій успіху 2.1.1) і щоб кожен компонент повідомляв своє ім’я та роль (4.1.2). Варто використовувати button, a, input і label за їхнім призначенням і пробувати кожен новий екран лише з клавіатурою, перш ніж він вийде в реліз.

Що почитати далі

Словник