Edge рантайм. Один из главных функционалов компании Vercel — компании, которая разработала и развивает next.js. Тем не менее, её влияние по edge рантайму вышло далеко за рамки её фреймворков и утилит. Edge рантайм работает и в недавно купленном Vercel Svelte, и в nuxt, и в более чем 30 других фронтенд фреймворках. Эта статья будет посвящена edge рантайму — что это, как это используется в Vercel, какими возможностями дополняет next.js и какие решения сделал я, чтобы эти возможности расширить.

Vercel Edge Network

Простыми словами Edge рантайм представляет из себя сеть доставки контента (CDN/распределённая инфраструктура), то есть множество точек по всему миру. Таким образом пользователь взаимодействует не с единым сервером (который может лежать в офисе компании на другом конце света), а с ближайшей к нему точкой сети.

Разница обращения напрямую на сервер или через Edge Network
Разница обращения напрямую на сервер или через Edge Network

При этом, это не копии приложения, а отдельный функционал, который может работать между клиентом и сервером. То есть это своего рода мини-сервера со своими особенностями (о которых будет рассказано позже).

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

Обработка запросов через Edge Network
Обработка запросов через Edge Network

Конечно, сама эта концепция не является заслугой 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, кеширование, реврайты и т. д.

Работает это всё в следующем порядке:

  1. headers из next.config.js;

  2. redirects из next.config.js;

  3. Middleware;

  4. beforeFiles реврайты from next.config.js;

  5. Файлы и статичные сегменты (public/_next/static/pages/app/, etc.);

  6. afterFiles реврайты из next.config.js;

  7. Динамические сегменты (/blog/[slug]);

  8. 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 обычно работает в следующем порядке:

Порядок обработки запроса в Vercel, источник - vercel.com
Порядок обработки запроса в Vercel, источник — vercel.com

Vercel после сборки отправляет новый код Edge рантайма (для которого недавно сделали компиляцию в машинный код) на точки сети и те сразу начинают работать с новым кодом.

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

На этих точках всегда лежит логика кеширования, также, если в проекте есть реврайты, редиректы, middleware или сегменты в Edge рантайме — после сборки он отправит это всё на edge сервера.

Далее Edge рантайм начнёт обрабатывать запросы: проверит реврайты и редиректы → проведёт через middleware → посмотрит есть ли в кеше → если сегмент в edge рантайме — выполнит его у себя, если нет — отправит запрос на оригинальный сервер (но про все эти порядки в Edge рантайме и внутренности Vercel нигде не пишет, но вероятнее всего это проходит именно так).

То есть, суммарно — хорошо использовать Edge рантайм, когда можно сделать всю обработку внутри Edge среды (Запрос будет Client → Edge). Если же нужно ходить к основному серверу (допустим к БД которая подключена внутри проекта, или по какой‑то причине нужно читать файлы) — это будет не выгодно. То есть запрос всё равно будет Client → Edge → Server. А раз всё равно нужно ходить на сервер — лучше сразу всю обработку сделать в нём — в нём есть весь кеш, рядом лежит БД, рядом вся система, да и в целом больше возможностей.

Ожидаемые изменения в edge рантайме

Несмотря на то, что edge рантайм — одна из ключевых возможностей Vercel как хостинга, команда активно его пересматривает. При чём не только само применение, но и необходимость в целом. Так, недавно VP Vercel — Ли Робинсон в своём твите поделился, что Vercel [как компания] перестала использовать edge рантайм во всех своих сервисах и вернулась к nodejs рантайму. Также команда ожидает, что экспериментальный частичный пререндер (PPR) будет настолько эффективен, что генерация в edge рантайме окончательно потеряет ценность.

И именно PPR вместе с продвинутым кешированием вытеснил edge рантайм на второй план. То есть раньше страница рендерилась целиком что на сервере, что в edge рантайме. Edge рантайм выигрывал именно за счёт более близкого расположения. Теперь же, страницы в большей части генерируются предварительно. Затем, при запросе, рендерятся отдельные динамические части и кешируются. Кеш, в свою очередь, в edge рантайме для каждой точки свой, когда на сервере он един для всех пользователей.

Ну и, конечно, у сервера есть доступы к окружению, БД и файловой системе. Поэтому если странице нужны эти данные — nodejs рантайм значительно выигрывает (собрать всё в одном окружении быстрее, чем каждый раз делать запросы на сервер из edge среды).

Скорее всего Vercel введёт новые приоритеты в своих прайсингах, перестроив их вокруг частичного пререндера. Возможно с их изменением твитов со счетами в десятки тысяч долларов станет меньше (но это не точно).

Инвойс за использование Vercel в $100000, источник - твит фаундера Cara.
Инвойс за использование Vercel в $100000, источник - твит фаундера Cara.

Помимо этого, недавно команда 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.

Спасибо @Dartess за комментарий с дельными вопросами (после которых статья была дополнена).

Комментарии (7)


  1. flancer
    16.07.2024 10:54
    +1

    а сделав мини-приложения, способные даже рендерить страницы в ближайшей к пользователю точке.

    Следующий этап - рендерить страницы непосредственно в устройстве пользователя.


    1. ZimM
      16.07.2024 10:54

      Это и так делается благодаря природе React) речь о изначальной версии страницы, чтобы юзер начал видеть контент вместо ожидания на белом экране


      1. Vordgi Автор
        16.07.2024 10:54

        Именно. Это пререндер статики. Чтобы пользователь и роботы при обращении получили сразу готовый html. А уже после этого на клиенте загрузиться вся логика, пройдёт гидрация и соберётся реативное приложение.

        Ну а edge может дать прирост во времени ответа сервера (и, как следствие, метрики FCP) - что будем ждать эту статику не 500мс, а 300мс. Учитывая что задержка сейчас часто дольше чем сама загрузка - в некоторых случаях может дать неплохой буст (но, конечно, много переменных).


  1. Dartess
    16.07.2024 10:54

    Как же сложно... Так и не понял, в каком случае вот прям нужно перекладывать работу на Edge, и как именно оно будет работать. Если npm start запускает именно сервер, что (и как) запускает edge рантаймы? Как они взаимодействуют с основным сервером? Версель как-то детектит, что в проекте есть использование edge рантаймов и начинает перенаправлять запросы туда вместо прямых запросов на сервер? Все запросы или не все? А сколько под проект выделить точек в своей network (и где) версель сам решает по метрикам проекта? Или я вообще не так понял это всё?


    1. Vordgi Автор
      16.07.2024 10:54

      Как они взаимодействуют с основным сервером? Версель как-то детектит, что в проекте есть использование edge рантаймов и начинает перенаправлять запросы туда вместо прямых запросов на сервер? Все запросы или не все? А сколько под проект выделить точек в своей network (и где) версель сам решает по метрикам проекта?

      Тут вопросы про CDN в целом. Не стал особо затрагивать тему, так как хотел базовую статью про отличительные особенности именно для фронтендеров.
      Сам Vercel использует edge рантайм вообще для всех приложений и всех запросов. То есть вы подключили домен и Vercel сразу сказал, что этот домен лежит ещё и на этих вот точках по миру. В следующий раз, когда пользователь зайдёт на страницу - провайдер пойдёт спрашивать, а где этот домен лежит, ему в ответе прилетят доступные места и он выберет ближайшую и пойдёт на неё.

      На этих точках всегда лежит логика кеширования, также, если в проекте есть реврайты, редиректы, middleware или сегменты в edge рантайме - после сборки он отправит это всё на edge сервера.

      Дальше edge рантайм это обработает: проверит реврайты и редиректы -> проведёт через middleware -> посмотрит есть ли в кеше -> если сегмент в edge рантайме - выполнит его у себя, если нет - отправит запрос на оригинальный сервер (но про все эти порядки в Edge рантайме и внутренности Vercel нигде не пишет, но как вижу это именно так).

      Если npm start запускает именно сервер, что (и как) запускает edge рантаймы?

      Vercel после сборки просто отправляет новый код edge рантайма (они недавно, кстати, сделали его компиляцию в машинный код) на сервера и те просто начинают работать с этим кодом.

      Так и не понял, в каком случае вот прям нужно перекладывать работу на Edge

      Ну по этой части я писал - хорошо использовать когда можно сделать всю обработку внутри (Запрос будет Client -> Edge). Если же нужно ходить к основному серверу (допустим на БД которая подключена внутри проекта, или файлы читать по какой-то причине) - это будет не выгодно. То есть запрос всё равно будет Client -> Edge -> Server. А раз всё равно ходите на сервер - лучше сразу всю обработку сделать в нём - в нём есть весь кеш, БД лежит рядом, система рядом, да и в целом возможностей больше.

      Надеюсь ответил на вопросы!


      1. Dartess
        16.07.2024 10:54

        Да, стало немного понятнее... Осталось понять как это всё тестировать и дебажить. Подумаю нам тем чтобы попробовать что-то переложить на edge. Спасибо за статью!


        1. Vordgi Автор
          16.07.2024 10:54

          Спасибо за комментарий! Добавил эти моменты также в саму статью