Edge рантайм. Один из главных функционалов компании Vercel — компании, которая разработала и развивает next.js. Тем не менее, её влияние по edge рантайму вышло далеко за рамки её фреймворков и утилит. Edge рантайм работает и в недавно купленном Vercel Svelte, и в nuxt, и в более чем 30 других фронтенд фреймворках. Эта статья будет посвящена edge рантайму — что это, как это используется в Vercel, какими возможностями дополняет next.js и какие решения сделал я, чтобы эти возможности расширить.
Vercel Edge Network
Простыми словами Edge рантайм представляет из себя сеть доставки контента (CDN/распределённая инфраструктура), то есть множество точек по всему миру. Таким образом пользователь взаимодействует не с единым сервером (который может лежать в офисе компании на другом конце света), а с ближайшей к нему точкой сети.
При этом, это не копии приложения, а отдельный функционал, который может работать между клиентом и сервером. То есть это своего рода мини-сервера со своими особенностями (о которых будет рассказано позже).
Эта система позволяет пользователям ходить не сразу на ваш далёкий сервер, а на близлежащую точку. В ней принимаются решения по а/б тестам, делаются проверки авторизации, кешируются запросы, возвращаются ошибки и многое другое. Уже после этого, если нужно, запрос пойдёт на сервер, сразу за нужной информацией. Иначе же пользователь в минимальные сроки получает ошибку или, например, редирект.
Конечно, сама эта концепция не является заслугой Vercel. Так умеет и CloudFlare, и Google Cloud CDN и множество других решений. Однако, Vercel, своим влиянием на фреймворки, вывел это на новый уровень, развернув не просто промежуточный роутер на уровне CDN, а сделав мини-приложения, способные даже рендерить страницы в ближайшей к пользователю точке. И самое главное — сделать это можно просто добавив привычные JS файлы в проект.
Edge-рантайм в next.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
реврайты fromnext.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 рантайма в Vercel
Как уже говорилось, edge рантайм можно назвать мини-приложениями. А мини они потому, что работают на node.js V8 (на котором работают например Google Chrome и Electron). Это их ключевая особенность, от которой зависят не только возможности предыдущего раздела, но и запреты.
А именно, в edge рантайме нельзя:
-
Делать действия с файловой системой;
-
Взаимодействовать с окружением сервера;
-
Вызывать
require
. Можно использовать только ES модули. От этого есть дополнительные ограничения на сторонние решения.
Полный список поддерживаемых API и ограничений можно найти на странице документации next.js.
Таким образом Vercel Edge Network может отвечать, например, за:
-
Роутинг;
-
Рендер страниц;
-
Выполнение API роутов;
-
Кеширование.
Edge рантайм выступает первым этапом обработки сегмента и наиболее эффективен он для ситуаций, когда вся обработка может пройти внутри edge контейнера. Например для редиректов или возврата закешированных данных. Весь процесс обработки в Vercel обычно работает в следующем порядке:
Ожидаемые изменения в edge рантайме
Несмотря на то, что edge рантайм — одна из ключевых возможностей Vercel как хостинга, команда активно его пересматривает. При чём не только само применение, но и необходимость в целом. Так, недавно VP Vercel — Ли Робинсон в своём твите поделился, что Vercel [как компания] перестала использовать edge рантайм во всех своих сервисах и вернулась к nodejs рантайму. Также команда ожидает, что экспериментальный частичный пререндер (PPR) будет настолько эффективен, что генерация в edge рантайме окончательно потеряет ценность.
И именно PPR вместе с продвинутым кешированием вытеснил edge рантайм на второй план. То есть раньше страница рендерилась целиком что на сервере, что в edge рантайме. Edge рантайм выигрывал именно за счёт более близкого расположения. Теперь же, страницы в большей части генерируются предварительно. Затем, при запросе, рендерятся отдельные динамические части и кешируются. Кеш, в свою очередь, в edge рантайме для каждой точки свой, когда на сервере он един для всех пользователей.
Ну и, конечно, у сервера есть доступы к окружению, БД и файловой системе. Поэтому если странице нужны эти данные — nodejs рантайм значительно выигрывает (собрать всё в одном окружении быстрее, чем каждый раз делать запросы на сервер из edge среды).
Скорее всего Vercel введёт новые приоритеты в своих прайсингах, перестроив их вокруг частичного пререндера. Возможно с их изменением твитов со счетами в десятки тысяч долларов станет меньше (но это не точно).
Помимо этого, недавно команда Next.js поделилась твитом о переработке middleware. Весьма вероятно ему, также как и сегментам, добавят выбор среды исполнения. Опять же, учитывая что вне Vercel middleware работает как часть сервера — это очень логичное решение. Также возможно, что вместе с изменениями будет добавлен отдельный middleware для API роутов.
Расширение Edge рантайма
Я автор ряда пакетов под next.js nimpl.tech. Я уже упоминал геттеры с информацией о текущей странице в статье “Next.js App Router. Опыт использования. Путь в будущее или поворот не туда”, библиотеке переводов в “Больше библиотек богу библиотек или как я переосмыслил i18n [next.js v14]”, пакеты для кэша в “Кеширование next.js. Дар или проклятие”. Но в этом семействе есть также и пакеты, построенные именно под edge рантайм — router и middleware-chain.
@nimpl/router
Как уже говорилось, 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.
@nimpl/middleware-chain
Работая с готовыми решениями или создавая свои — из раза в раз я сталкивался с проблемой их объединения в одном 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.
ссылка на оригинал статьи https://habr.com/ru/articles/829074/
Добавить комментарий