Vercel VS Edge VS Next. Что такое Edge, зачем, как и куда
- среда, 17 июля 2024 г. в 00:00:07
Edge рантайм. Один из главных функционалов компании Vercel - компании, которая разработала и развивает next.js. Тем не менее, её влияние по edge рантайму вышло далеко за рамки её фреймворков и утилит. Edge рантайм работает и в недавно купленном Vercel Svelte, и в nuxt, и в более чем 30 других фронтенд фреймворках. Эта статья будет посвящена edge рантайму - что это, как это используется в Vercel, какими возможностями дополняет next.js и какие решения сделал я, чтобы эти возможности расширить.
Простыми словами Edge рантайм представляет из себя сеть доставки контента (CDN/распределённая инфраструктура), то есть множество точек по всему миру. Таким образом пользователь взаимодействует не с единым сервером (который может лежать в офисе компании на другом конце света), а с ближайшей к нему точкой сети.
При этом, это не копии приложения, а отдельный функционал, который может работать между клиентом и сервером. То есть это своего рода мини-сервера со своими особенностями (о которых будет рассказано позже).
Эта система позволяет пользователям ходить не сразу на ваш далёкий сервер, а на близлежащую точку. В ней принимаются решения по а/б тестам, делаются проверки авторизации, кешируются запросы, возвращаются ошибки и многое другое. Уже после этого, если нужно, запрос пойдёт на сервер, сразу за нужной информацией. Иначе же пользователь в минимальные сроки получает ошибку или, например, редирект.
Конечно, сама эта концепция не является заслугой Vercel. Так умеет и CloudFlare, и Google Cloud CDN и множество других решений. Однако, Vercel, своим влиянием на фреймворки, вывел это на новый уровень, развернув не просто промежуточный роутер на уровне CDN, а сделав мини-приложения, способные даже рендерить страницы в ближайшей к пользователю точке. И самое главное - сделать это можно просто добавив привычные JS файлы в проект.
В next.js пожалуй главным функционалом этой среды является файл middleware. Любой сегмент (API или страница) также могут исполняться в edge рантайме. Но перед их описанием, немного о next.js сервере.
Next.js - это фуллстак фреймворк. То есть он содержит и клиентское приложение, и сервер. Запуская next.js (next start
) - запускается именно сервер и уже он отвечает за выдачу страниц, работу API, кеширование, реврайты и т.д.
Работает это всё в следующем порядке:
headers
из next.config.js
;
redirects
из next.config.js
;
Middleware;
beforeFiles
реврайты from next.config.js
;
Файлы и статичные сегменты (public/
, _next/static/
, pages/
, app/
, etc.);
afterFiles
реврайты из next.config.js
;
Динамические сегменты (/blog/[slug]
);
fallback
реврайты из next.config.js
.
Когда определяется, что текущей запрос доходит именно до сегмента (а не, например, до редиректа) - запускается его обработка (это либо возврат статически собранного сегмента, либо чтение из кеша, либо его выполнение и возврат результата).
В Vercel, вероятно, весь этот цикл может пройти в edge рантайме. Тем не менее, действительно интересны здесь именно пункты 3, 5 и 7.
Сам middleware в базовой имплементации выглядит так:
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url));
}
В нём, например, можно:
Сделать запросы (напр. чтобы узнать данные из третьих сервисов);
Выполнить реврайт или редирект (напр. чтобы провести а/б тест или проверить авторизацию);
Вернуть некое тело (напр. чтобы отобразить базовую заглушку при определённых ситуациях);
Прочитать и/или изменить заголовки и куки (напр. сохранить или прочитать информацию о доступах).
Подробнее про области применения можно почитать в документации next.js по middleware.
Тоже самое можно делать и в сегментах (т.е. API и страницы). Чтобы сегмент работал в edge рантайме нужно экспортировать из файла сегмента:
export const runtime = 'edge';
Таким образом сегмент будет исполняться в edge рантайме, а не на самом сервере.
Однако, стоит сделать важную оговорку. Всё описанное выше само по себе не является полноценным edge рантаймом. В Edge Network это распределится только при деплое сервиса в Vercel.
Также, помимо всех этих возможностей, у edge рантайма есть и ряд ограничений. Например, несмотря на то, что при запуске приложения вне Vercel, edge рантайм является частью сервера - взаимодействовать с этим сервером не удастся. И сделано это так потому, что разрабатывалось это именно под Vercel Edge Network.
Как уже говорилось, edge рантайм можно назвать мини-приложениями. А мини они потому, что работают на node.js V8 (на котором работают например Google Chrome и Electron). Это их ключевая особенность, от которой зависят не только возможности предыдущего раздела, но и запреты.
А именно, в edge рантайме нельзя:
Делать действия с файловой системой;
Взаимодействовать с окружением сервера;
Вызывать require
. Можно использовать только ES модули. От этого есть дополнительные ограничения на сторонние решения.
Полный список поддерживаемых API и ограничений можно найти на странице документации next.js.
Таким образом Vercel Edge Network может отвечать, например, за:
Роутинг;
Рендер страниц;
Выполнение API роутов;
Кеширование.
Edge рантайм выступает первым этапом обработки сегмента и наиболее эффективен он для ситуаций, когда вся обработка может пройти внутри edge контейнера. Например для редиректов или возврата закешированных данных. Весь процесс обработки в Vercel обычно работает в следующем порядке:
Несмотря на то, что edge рантайм - одна из ключевых возможностей Vercel как хостинга, команда активно его пересматривает. При чём не только само применение, но и необходимость в целом. Так, недавно VP Vercel - Ли Робинсон в своём твите поделился, что Vercel [как компания] перестала использовать edge рантайм во всех своих сервисах и вернулась к nodejs рантайму. Также команда ожидает, что экспериментальный частичный пререндер (PPR) будет настолько эффективен, что генерация в edge рантайме окончательно потеряет ценность.
И именно PPR вместе с продвинутым кешированием вытеснил edge рантайм на второй план. То есть раньше страница рендерилась целиком что на сервере, что в edge рантайме. Edge рантайм выигрывал именно за счёт более близкого расположения. Теперь же, страницы в большей части генерируются предварительно. Затем, при запросе, рендерятся отдельные динамические части и кешируются. Кеш, в свою очередь, в edge рантайме для каждой точки свой, когда на сервере он един для всех пользователей.
Ну и, конечно, у сервера есть доступы к окружению, БД и файловой системе. Поэтому если странице нужны эти данные - nodejs рантайм значительно выигрывает (собрать всё в одном окружении быстрее, чем каждый раз делать запросы на сервер из edge среды).
Скорее всего Vercel введёт новые приоритеты в своих прайсингах, перестроив их вокруг частичного пререндера. Возможно с их изменением твитов со счетами в десятки тысяч долларов станет меньше (но это не точно).
Помимо этого, недавно команда Next.js поделилась твитом о переработке middleware. Весьма вероятно ему, также как и сегментам, добавят выбор среды исполнения. Опять же, учитывая что вне Vercel middleware работает как часть сервера - это очень логичное решение. Также возможно, что вместе с изменениями будет добавлен отдельный middleware для API роутов.
Я автор ряда пакетов под next.js nimpl.tech. Я уже упоминал геттеры с информацией о текущей странице в статье “Next.js App Router. Опыт использования. Путь в будущее или поворот не туда”, библиотеке переводов в “Больше библиотек богу библиотек или как я переосмыслил i18n [next.js v14]”, пакеты для кэша в “Кеширование next.js. Дар или проклятие”. Но в этом семействе есть также и пакеты, построенные именно под edge рантайм - router и middleware-chain.
Как уже говорилось, edge рантайм лучше всего работает если он может обработать весь запрос в замкнутом мини-приложении. Во всех остальных случаях это ненужный шаг, так как запрос все равно будет ходить на сервер, но более длинным путём.
Одна из таких задач - роутинг. В роутинг входят также реврайты, редиректы, basePath и i18n из next.config.js
.
Их главные проблема в том, что задаются они лишь единожды - в файле конфигурации - для всего приложения, а i18n полон багов. Поэтому в том числе в App Router нет информации об опции i18n и документация рекомендуют использовать для этой задачи middleware. Но такое разделение означает, что редиректы из конфига и i18n роутинг из middleware обрабатываются отдельно. От этого могут происходить двойные редиректы (сперва выполнится редирект из конфига, затем редирект из middleware) и вылезать различные неожиданные артефакты.
Чтобы этого избежать весь этот функционал стоит собрать в одном месте. И, как рекомендует документация для i18n - таким местом должен стать middleware.
import { createMiddleware } from '@nimpl/router';
export const middleware = createMiddleware({
redirects: [
{
source: '/old',
destination: '/',
permanent: false,
},
],
rewrites: [
{
source: '/home',
destination: '/',
locale: false,
},
],
basePath: '/doc',
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
});
Привычные Next.js редиректы, реврайты, basePath и i18n настройки, но на уровне edge рантайма. Документация пакета @nimpl/router.
Работая с готовыми решениями или создавая свои - из раза в раз я сталкивался с проблемой их объединения в одном middleware. То есть когда к одному проекту нужно подключить два и более готовых middleware.
Проблема в том, что middleware в next.js это не тоже самое, что в express или koa - он сразу возвращает финальный результат. Поэтому каждый пакет просто создаёт финальный middleware. Например у next-intl это выглядит следующим образом:
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'de'],
defaultLocale: 'en',
});
Я не первый столкнулся с этой проблемой, и в npm можно найти уже готовые решения. Все они работают через свои собственные API - сделанные в стиле express или в их собственном видении. Они полезны, хорошо реализованы и удобны. Но только в тех случаях, когда вы можете обновить каждый используемый middleware.
Однако, есть много ситуаций, когда нужно добавить уже готовые решения. Обычно в задачах этих решений можно найти "добавить поддержку для добавления пакета цепи A”, “работать с пакетом цепи B". Именно для таких ситуаций и создан @nimpl/middleware-chain.
Этот пакет позволяет создавать цепь нативных next.js middleware без каких-либо модификаций (то есть, можно добавить любой готовый middleware в цепь).
import { default as authMiddleware } from "next-auth/middleware";
import createMiddleware from "next-intl/middleware";
import { chain } from "@nimpl/middleware-chain";
const intlMiddleware = createMiddleware({
locales: ["en", "dk"],
defaultLocale: "en",
});
export default chain([
intlMiddleware,
authMiddleware,
]);
Цепь обрабатывает каждый middleware поочерёдно. При обработке собираются все модификации до завершения цепи или пока какой-нибудь элемент цепи не вернёт FinalNextResponse.
export default chain([
intlMiddleware,
(req) => {
if (req.summary.type === "redirect") return FinalNextResponse.next();
},
authMiddleware,
]);
Это не Koa и не Express, это пакет для next.js, в его уникальном стиле и в формате его API. Документация пакета @nimpl/middleware-chain.
Ну и в завершении позволю себе немного ссылок.
Мой хабр с другими полезными статьями | nimpl.tech с документацией пакетов | github с кнопкой звёздочек
Карта из точек, используемая в качестве фона изображений в начале статьи сделана mocrovector с freepik.