EverProduct
Фронтенд

Этап 04 · Приложение

Другой конец провода: данные, маршруты и место выполнения кода

Форму настоящего приложения задают два вопроса: как человек переходит между экранами и в какой момент собирается HTML. Фреймворки приходят и уходят, эти два вопроса — нет.

До сих пор у тебя были компоненты и состояние. Приложение добавляет две вещи: несколько экранов с адресами и сервер на другом конце провода.

Отсюда два решения, и они определяют всё остальное. Как человек переходит между экранами? И — то, что пропускают, а потом за это платят, — в какой момент и на чьей машине собирается HTML?

Маршрутизация: адрес — часть твоего интерфейса

На традиционном сайте каждая ссылка — полная перезагрузка: браузер выбрасывает страницу, спрашивает сервер и рисует новый документ. В приложении роутер это перехватывает, меняет адрес без перезагрузки и подменяет ту часть экрана, которая отличается.

Составные части везде одинаковы: маршруты, сопоставляющие шаблон адреса экрану, параметры в пути, строка запроса для фильтров и поиска, вложенные раскладки, чтобы общая оболочка переживала переход, и маршрут 404 для всего непопавшего.

Три вещи портят регулярно, и все три замечают пользователи:

Используй настоящие ссылки. По якорю с href можно кликнуть средней кнопкой, открыть в новой вкладке, скопировать, и его читает робот. div с обработчиком, дёргающим роутер, — ссылка, работающая только у тех, кто ведёт себя ровно так, как ты вообразил; тот же довод, что в статье про семантику, только теперь на кону вся навигационная модель браузера.

Восстанавливай прокрутку и управляй фокусом. Настоящая перезагрузка сбрасывает прокрутку и объявляет новый документ. Клиентский переход не делает ни того ни другого, пока ты сам этого не сделаешь: без управления фокусом человек со скринридером переходит по ссылке и не слышит ничего, оставаясь в старой позиции.

Считай адрес состоянием. Из предыдущей статьи: фильтры, поисковые запросы, вкладки и пагинация живут там. Это и делает вид пригодным для отправки ссылкой, а кнопку «назад» честной.

Разговор с сервером

Чаще всего ты будешь звать HTTP API и получать JSON. Словарь небольшой: ресурс, обозначенный путём, метод, описывающий намерение (получить, создать, изменить, удалить), код статуса, описывающий исход, и тело.

Коды статусов стоит знать семействами, а не числами: 2xx — сработало, 3xx — иди в другое место, 4xx — ошибся ты (401 не аутентифицирован, 403 не разрешено, 404 нет такого, 422 некорректные данные), 5xx — ошибся сервер. Это различие ведёт твой интерфейс: 4xx обычно означает показать человеку конкретное и действенное сообщение, 5xx — извиниться и предложить повтор.

Ты можешь встретить и GraphQL, где клиент просит в одном запросе ровно нужные поля. Он решает настоящие проблемы — избыточную выборку и водопады зависимых запросов — ценой более тяжёлого клиента и более сложного кэширования. В любом случае привычка одна и та же: контракт с сервером — то, что нужно знать точно. Какие поля необязательны, как выглядит пустой результат, что лежит в теле ошибки. Большинство интеграционных багов — это допущение об этом контракте, которое никто не записал.

Две вещи, которые собьют тебя с толку ровно один раз

CORS. Ты зовёшь API со своей страницы, и браузер блокирует запрос сообщением про другое происхождение. Прочитай это один раз и сэкономь день: браузер не позволяет странице прочитать ответ с другого источника, пока тот сервер явно не разрешит это заголовком. Это защита пользователя, и — вот та часть, из-за которой воюют часами — починить это во фронтенде нельзя. Ни опцией fetch, ни заголовком с клиента. Либо сервер разрешает твой источник, либо ты идёшь через прокси, который разрешает. Отключение защиты браузера, чтобы оно «прошло», — не починка, а способ сделать разработку непохожей на прод.

Аутентификация. Две частые формы. Токен, который клиент хранит и шлёт с каждым запросом, или кука, которую браузер прикладывает сам. Токен в локальном хранилище читается любым скриптом, который внедрится в страницу, что делает его подарком для XSS; куку с httpOnly JavaScript не прочитает вовсе — ценой необходимости защиты от CSRF. Здесь есть размены, и их место в сфере «Безопасность».

Но одно правило абсолютно, и именно его фронтендеры чаще всего понимают неверно: клиент не проверяет права. Спрятанная кнопка администратора — вежливость, а не контроль. Обратиться к эндпоинту напрямую может кто угодно. Любая значимая проверка повторяется на сервере, а если не повторяется, дело было не в кнопке.

Откуда берётся HTML

Вот вопрос, ради которого написана эта статья, и он надёжнее всего отделяет тех, кто выбирает стек, от тех, кто его унаследовал.

Клиентский рендеринг. Сервер шлёт почти пустую страницу и пакет JavaScript, а браузер строит всё. Модель простая, но человек не видит ничего, пока JavaScript не загрузится и не выполнится, а поисковики видят пустую страницу, если не выполняют скрипты. Уместно для приложений за логином, где первая отрисовка важна меньше, чем последующее взаимодействие.

Серверный рендеринг. Сервер собирает HTML на каждый запрос и шлёт настоящую страницу, которую потом «оживляют» в браузере. Быстрая первая отрисовка, работает для поиска, стоит серверного времени на каждый запрос и требует, чтобы данные были доступны в момент запроса. Уместно, когда содержимое персональное и при этом должно быть быстрым.

Статическая генерация. HTML собирается один раз при деплое и отдаётся файлами, обычно с CDN. Быстрее и дешевле не бывает, устойчивее тоже, — но содержимое одинаково для всех до следующей сборки. Уместно для контентных сайтов, документации, промо, блогов.

Гибриды. Пересборка статических страниц по расписанию или по требованию, потоковая отдача частей серверной страницы по мере прихода данных, почти статический HTML с небольшими интерактивными островами. Они существуют потому, что настоящие сайты — смесь: страница товара почти статична, корзина нет.

Любая стратегия рендеринга отвечает на один вопрос: в какой момент и кем собирается HTML — сборкой, сервером или браузером?

Выучи эту ось — и названия продуктов перестанут иметь значение. Мета-фреймворки — Next, Nuxt, SvelteKit, Remix, Astro — существуют ровно затем, чтобы дать маршрутизацию, загрузку данных и выбор стратегии рендеринга в одном согласованном наборе, и соревнуются они именно по этой оси, а не в чём-то таинственном.

Текущее направление движения знать полезно: больше рендеринга на сервере, меньше отправляемого JavaScript и разметка на клиент только по-настоящему интерактивных частей. Это откат после десятилетия переноса всего в браузер, и движет им то, что не менялось никогда: байты стоят времени, и работу делает чей-то телефон.

Как выбирать, в трёх фразах

Контентный сайт — промо, документация, блог: статика, и не усложняй. Приложение за логином: клиентский рендеринг вполне подходит, поскольку поиск и первая отрисовка значат меньше, чем следующие двадцать минут. Всё публичное, персональное и коммерческое: гибрид, и вот здесь размышления действительно окупаются.

И четвёртое: что бы ты ни выбрал, знай, откуда и когда приходит каждый кусок данных. Большинство катастроф с производительностью — это страница, которая может начать грузить данные только после того, как закончилось всё остальное.

Кэширование, слой за слоем

Кэш данных из предыдущей статьи — не единственный слой, и знание всей стопки предотвращает вполне определённое сумасшествие. Браузер кэширует файлы по заголовкам. CDN кэширует ответы рядом с пользователем. Твой слой данных кэширует результаты API в памяти. А сервис-воркер, если он есть, может кэшировать настолько агрессивно, что отдаст страницу без сети — и настолько же агрессивно отдаст версию, которую ты удалил три недели назад.

Когда пользователи видят устаревшее, вопрос не «почему закэшировано», а «какой из четырёх». Назвать слой — и есть починка.

На практике

Нарисуй поток данных для одного экрана. Какие запросы уходят, в каком порядке, что от чего зависит и где это рисуется. Десять минут на бумаге находят водопады, невидимые в коде.

Открой панель Network и посчитай походы туда-обратно до момента, когда экраном можно пользоваться. Число обычно больше ожидаемого, и атаковать надо последовательные.

Положи фильтры в адрес и нажми «назад». Маленькое изменение с немедленным доказательством.

Прочитай документацию своего API как следует, один раз, вместо того чтобы месяц выяснять контракт методом проб. Это то самое различие между узнаванием и знанием из сферы «Как учиться»: листать, пока не заработает, оставляет тебя с формой, о которой невозможно рассуждать.

Объясни свою стратегию рендеринга одной фразой: «HTML собирается там-то тем-то, потому что…». Не получается — значит, ты её унаследовал, а не выбрал, и это нормально ровно до того дня, когда именно её надо менять.

Проверь себя

Закрой статью и ответь своими словами:

  1. Что заменяет клиентский роутер и какие три вещи он ломает, если о них не позаботиться?
  2. Что означают семейства 4xx и 5xx для того, что ты покажешь пользователю?
  3. Что такое CORS и почему его нельзя починить во фронтенде?
  4. Почему спрятанная кнопка — не проверка прав?
  5. Дай по одной фразе на клиентский рендеринг, серверный рендеринг и статическую генерацию с подходящим случаем для каждого.
  6. На какой один вопрос отвечают все стратегии рендеринга?
  7. Назови четыре слоя кэширования между твоими данными и экраном пользователя.

Коротко

  • Приложение добавляет к компонентам и состоянию два решения: как люди переходят между экранами и когда собирается HTML.
  • Маршрутизация — это настоящие ссылки, восстановленная прокрутка, управляемый фокус и адрес, несущий фильтры, поиск и пагинацию.
  • Знай контракт своего API точно; семейства кодов статуса подсказывают, показать ли действенное сообщение или извиниться и предложить повтор.
  • CORS — решение сервера, и с клиента он не чинится, а клиент не проверяет права, а только отображает их.
  • Клиентский рендеринг уместен за логином, серверный — для быстрого персонального содержимого, статика — для контентных сайтов, а настоящие сайты обычно гибридны.
  • Любая стратегия отвечает на один вопрос — в какой момент и на чьей машине собирается HTML, — и мета-фреймворки соревнуются именно по этой оси.
  • Индустрия движется назад к рендерингу на сервере и меньшему объёму JavaScript, потому что байты стоят времени на чужом телефоне.
  • Устаревшее содержимое — всегда один из четырёх слоёв кэша: браузер, CDN, слой данных, сервис-воркер.