Перейти к содержимому

Edge Runtime

Edge Runtime — это изолированная среда выполнения JavaScript на базе V8 для SSR и Edge Functions. Поддерживает Web APIs и платформенные APIs ONREZA.

Edge Runtime полностью поддерживает стандартные Web APIs:

APIОписание
fetch()HTTP запросы с SSRF защитой
Request / ResponseHTTP объекты
HeadersHTTP заголовки
FormDataForm data для POST запросов
APIОписание
ReadableStream / WritableStreamПотоки чтения/записи
TransformStreamТрансформация потоков
CompressionStream / DecompressionStreamСжатие (gzip, deflate)
APIОписание
crypto.subtleWeb Crypto API (encrypt, decrypt, sign, verify, digest)
crypto.getRandomValues()Криптографически безопасный random
crypto.randomUUID()UUID v4
APIОписание
TextEncoder / TextDecoderКодирование строк
Blob / FileБинарные данные
structuredClone()Глубокое копирование
URL / URLSearchParamsURL парсинг
URLPatternПаттерны для URL роутинга
APIОписание
setTimeout / clearTimeoutТаймауты
setInterval / clearIntervalИнтервалы
queueMicrotaskМикротаски
APIОписание
Event / EventTargetСобытийная модель
CustomEventПользовательские события
AbortController / AbortSignalОтмена операций
EventSourceServer-Sent Events клиент
APIОписание
BufferNode.js Buffer полифил
process.envДоступ к Environment Variables
process.versionNode.js version stub

Edge Runtime предоставляет доступ к платформенным API через SDK @onreza/runtime:

import { kv } from '@onreza/runtime/kv';
import { db } from '@onreza/runtime/db';
import { env } from '@onreza/runtime/env';
import { getContext, getGeo } from '@onreza/runtime/context';
import { imageUrl, generateSrcset } from '@onreza/runtime/image';
import { env, getEnv } from '@onreza/runtime/env';
const dbUrl = env.get("DATABASE_URL"); // string | undefined
const timeout = getEnv("TIMEOUT", "5000"); // string (с дефолтом)
if (env.has("API_KEY")) {
// ...
}
import { kv } from '@onreza/runtime/kv';
await kv.set("key", "value", { ttl: 3600 });
const value = await kv.get("key");
await kv.delete("key");
const { keys, cursor } = await kv.list({ prefix: "user:" });

Подробнее о KV Store

import { db } from '@onreza/runtime/db';
// Prepared statements с bind
const { results } = await db.prepare("SELECT * FROM users WHERE id = ?")
.bind(userId)
.all();
// Batch (атомарная транзакция)
await db.batch([
db.prepare("INSERT INTO users (name) VALUES (?)").bind("Alice"),
db.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
]);
// DDL
await db.exec("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL)");

Подробнее о D1 Database

ПараметрЛимит
Строк в ответе5,000
Максимальная длина SQL1 MB
import { getContext, getGeo, getClientIp, getRequestId } from '@onreza/runtime/context';
const ctx = getContext();
console.log(ctx.geo?.country); // "RU"
console.log(ctx.clientIp); // "203.0.113.42"
console.log(ctx.requestId); // UUID v7
import { imageUrl, generateSrcset } from '@onreza/runtime/image';
const url = imageUrl('/images/photo.jpg', { width: 800, quality: 85, format: 'webp' });
const srcset = generateSrcset('/images/photo.jpg', [640, 1080, 1920]);

Подробнее об Image Optimization

Edge Runtime автоматически проксирует входящие HTTP заголовки:

export default async function handler(request: Request) {
// Доступ к заголовкам
const userAgent = request.headers.get("user-agent");
const contentType = request.headers.get("content-type");
return new Response("OK");
}

Следующие заголовки добавляются платформой к каждому запросу:

HeaderОписаниеПример
x-onreza-request-idУникальный ID запроса018f3...
x-onreza-deployment-idID текущего деплояdep_abc123
x-onreza-project-idID проектаproj_xyz789
x-onreza-environmentОкружениеproduction
x-forwarded-forОригинальный IP клиента203.0.113.42
x-forwarded-protoОригинальный протоколhttps

Заголовок x-onreza-request-id содержит уникальный идентификатор, удобный для сортировки и трейсинга:

export default async function handler(request: Request) {
// Получить request ID для логирования/трейсинга
const requestId = request.headers.get("x-onreza-request-id");
// Пример: "018f3a4c-7b2d-7e8a-9f1b-2c3d4e5f6a7b"
// Уникальный идентификатор содержит timestamp в первых 48 битах
// Можно извлечь время создания запроса
const timestamp = parseInt(requestId!.slice(0, 8), 16);
const requestTime = new Date(timestamp * 1000);
// Логирование с request ID
console.log(`[${requestId}] Processing request...`);
// Передача в downstream сервисы для distributed tracing
const response = await fetch("https://api.example.com/data", {
headers: {
"X-Request-ID": requestId,
},
});
return new Response(JSON.stringify({ requestId, data: await response.json() }));
}

Вы можете устанавливать любые исходящие заголовки в ответе:

export default async function handler(request: Request) {
return new Response("Hello", {
headers: {
"Content-Type": "text/plain",
"Cache-Control": "public, max-age=3600",
"X-Custom-Header": "value",
},
});
}

Edge Runtime поддерживает bidirectional middleware — обработку запроса до и после основного handler:

middleware.ts
export default async function middleware(request: Request, next: () => Promise<Response>): Promise<Response> {
// --- Pre-processing ---
const startTime = Date.now();
const requestId = request.headers.get("x-onreza-request-id");
console.log(`[${requestId}] Request started: ${request.url}`);
// Проверка аутентификации
const authHeader = request.headers.get("authorization");
if (!authHeader) {
return new Response("Unauthorized", { status: 401 });
}
// Вызов следующего middleware или handler
const response = await next();
// --- Post-processing ---
const duration = Date.now() - startTime;
// Добавление заголовков
response.headers.set("x-response-time", `${duration}ms`);
response.headers.set("x-request-id", requestId!);
console.log(`[${requestId}] Request completed in ${duration}ms`);
return response;
}
ОграничениеОписание
Single next()next() может быть вызван только один раз
Buffer sizeМаксимальный размер buffered response: 5 MB
TimeoutОбщий таймаут на весь pipeline: 30 секунд
MemoryОбщий лимит памяти: 128 MB
// Неправильно: multiple next() calls
export default async function badMiddleware(request: Request, next: () => Promise<Response>) {
const response1 = await next();
const response2 = await next(); // Error: next() already called
return response1;
}
// Неправильно: обработка тела после next()
export default async function badMiddleware(request: Request, next: () => Promise<Response>) {
const response = await next();
const body = await response.text(); // Buffering > 5MB вызовет ошибку
return new Response(body.toUpperCase());
}
// Правильно: единственный вызов next()
export default async function goodMiddleware(request: Request, next: () => Promise<Response>) {
// Модификация request перед передачей
const modifiedRequest = new Request(request, {
headers: {
...request.headers,
"x-processed-at": Date.now().toString(),
},
});
const response = await next();
// Модификация response после получения
return new Response(response.body, {
status: response.status,
headers: {
...response.headers,
"x-middleware": "processed",
},
});
}

ONREZA автоматически применяет tiered per-IP rate limiting для защиты от DDoS и злоупотреблений:

  • Лимиты применяются на уровне edge-ноды по IP-адресу клиента
  • При превышении лимита возвращается HTTP 429 с заголовком Retry-After
  • Повторные нарушения приводят к временной блокировке IP

fetch() автоматически блокирует запросы к внутренним сетям:

// Заблокировано — приватная сеть
await fetch("http://10.0.0.1/internal");
// Заблокировано — localhost
await fetch("http://127.0.0.1:8080");
// Заблокировано — metadata облака
await fetch("http://169.254.169.254/latest/meta-data/");
// Разрешено — публичный интернет
await fetch("https://api.example.com/data");
ОперацияСтатус
Файловая системаЗапрещена (нет fs модуля)
Child processesЗапрещены (spawn, exec)
FFI / Native modulesЗапрещены
Сетевая информацияЗапрещена (os.networkInterfaces)
// server.ts или handler файл
export default async function handler(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello from ONREZA!" });
}
return new Response("Not Found", { status: 404 });
}
import { env } from '@onreza/runtime/env';
export default async function handler(request: Request): Promise<Response> {
const apiKey = env.get("API_KEY");
if (!apiKey) {
return new Response("API_KEY not configured", { status: 500 });
}
const data = await fetch("https://api.service.com/data", {
headers: { "Authorization": `Bearer ${apiKey}` },
});
return new Response(data.body);
}
  • Первый запрос: ~5ms
  • Повторные запросы: ~0.5ms (кэширование кода)
ПараметрЛимит
Таймаут выполнения30 секунд
Максимальная память128 MB
Максимальный размер ответа10 MB
Максимальный размер тела запроса10 MB
Максимальный размер сообщения (WebSocket/RPC)10 MB
// Используйте streaming для больших ответов
export default async function handler(request: Request) {
const stream = await fetchLargeDataStream();
return new Response(stream);
}
// Отменяйте неактуальные запросы
export default async function handler(request: Request) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch("https://api.example.com", {
signal: controller.signal,
});
clearTimeout(timeout);
return response;
} catch (e) {
if (e.name === "AbortError") {
return new Response("Timeout", { status: 504 });
}
throw e;
}
}
// Кэшируйте соединения (keep-alive автоматический)

ONREZA использует serverless-модель: каждый запрос выполняется в отдельном V8 isolate. Масштабирование полностью автоматическое — настройка не требуется.

  1. Запрос приходит на ближайшую edge-ноду
  2. Isolate создаётся за ~5ms (cold start) или переиспользуется за ~0.5ms
  3. Код выполняется в изолированной песочнице с ограничениями по памяти и времени
  4. Ответ отправляется клиенту, isolate освобождается

Платформа автоматически обрабатывает любую нагрузку: от единичных запросов до тысяч параллельных — без ручной настройки instances, auto-scaling policies или capacity planning.

СценарийПодходит?
SSR / API endpointsДа
Edge Functions (auth, geo-routing)Да
REST / GraphQL APIДа
Периодические задачи < 30sДа
Long-running tasks (> 30s)Нет — используйте внешние сервисы
WebSocket соединенияНет
Тяжёлые вычисления (ML, видео)Нет — превысит лимит памяти/времени
  • Минимизируйте cold start: держите размер бандла минимальным, избегайте тяжёлых зависимостей
  • Используйте KV Store: для кэширования результатов между запросами
  • Streaming: для больших ответов используйте ReadableStream вместо буферизации
  • Таймауты: устанавливайте AbortController для внешних fetch-запросов

Next.js автоматически использует Edge Runtime при деплое на ONREZA. Настройка не требуется.

app/api/hello/route.ts
export const runtime = 'edge'; // Явное указание
export async function GET(request: Request) {
const requestId = request.headers.get('x-onreza-request-id');
return Response.json({ requestId });
}
src/pages/api/hello.ts
export const prerender = false;
export async function GET({ request }: { request: Request }) {
const requestId = request.headers.get('x-onreza-request-id');
return new Response(JSON.stringify({ requestId }));
}
src/routes/api/hello/+server.ts
export async function GET({ request }) {
const requestId = request.headers.get('x-onreza-request-id');
return json({ requestId });
}
  • Runtime SDK — типизированный SDK для платформенных API (Edge Functions и Compute Apps)
  • Compute Apps — Next.js, Astro SSR и другие Bun-приложения
  • Image Optimization — оптимизация изображений
  • D1 Database — SQL база данных
  • KV Store — key-value хранилище