Перенос приложения с Deno Deploy на Cloudflare Workers: что едет один-в-один, а что переписывается

Deno Deploy закроют через примерно полгода после 9 октября 2026 — то есть ориентировочно весной 2027 (точный день берите из официального анонса в момент переезда, а не из этой статьи: сроки — «another year» и «six months» относительным интервалом, они двигаются). Если ваше приложение живёт на Deno Deploy и вы выбрали Путь A из первой статьи серии, эта статья — про то, как физически перенести код.

Главный вывод, к которому мы будем приходить всю статью и который экономит неделю сюрпризов: Deno и Workers — оба «современный JavaScript/TypeScript-рантайм», но совместимы они не полностью. И почти всё, что при переносе ломается, ломается не из-за языка (тот один и тот же TS/ES-модули, тот же async/await, та же Fetch API), а из-за способа достучаться до ресурсов: в Deno вы обращаетесь к системе напрямую — Deno.env, Deno.serve, URL-import, npm:; в Workers вам эти ресурсы присваивает платформа через аргумент env и через конфигурационный файл wrangler. Меняется не синтаксис функций, а архитектура доступа.

Разберём это по слоям: точка входа, конфигурация, данные, планировщик и вебсокеты, — и в конце соберём порядок переезда (runbook) с тестом и откатом. Код абстрактный (домены условные example.test); имена API и синтаксис wrangler — сверяйте по документации Cloudflare в день внедрения, это живая поверхность.

Про имена «воркеров». Здесь речь только про серверный рантайм Cloudflare Workers (entry-хендлер на edge), а не про браузерный Service Worker из соответствующей статьи — тот для офлайн-кэша в клиенте, тут он ни при чём.


Часть I. Ментальная модель: почему это не «переименовать функции»

Прежде чем трогать код, зафиксируем разницу мировоззрений — она объясняет, откуда возьмутся все правки ниже.

1. Deno: рантайм, которому принадлежат ресурсы

Deno — это самостоятельный исполняемый рантайм на сервере. Когда вы пишете:

const token = Deno.env.get("API_TOKEN");
const { socket, response } = Deno.upgradeWebSocket(request);
await Deno.kv.get("user:42");

вы напрямую зовёте API своего процесса: окружение — у Deno, файловая система и сеть — тоже, KV-клиент — глобальный. Программа сама решает, откуда что взять. Гибко, но каждый инстанс приложения сам собирается с инфраструктурой: дал описывал этот опыт как источник сложности Deno Deploy («multiple public clouds, multiple databases, deeply entangled services»).

2. Workers: модель «привязок» (bindings)

Cloudflare Workers переворачивают это. Ваш код — это обработчик, который платформа вызывает, передавая ему три вещи:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // request — входящий HTTP-запрос (стандартный Fetch API)
    // env     — ВСЕ ресурсы этого воркера: KV, D1, R2, секреты, vars — как свойства
    // ctx     — контекст выполнения (ctx.waitUntil(...) для фоновой работы)
    return new Response("ok");
  },
};

Никакого глобального Deno.*. Хотите ключ-значение-хранилище — вы не импортируете клиент, а объявляете биндинг в wrangler.toml, и он материализуется полем в env. Хотите секрет — он тоже приходит в env (но не через [vars], см. Часть III). Хотите подключить БД — [[d1_databases]], и в env.MY_DB появится объект с методами. Это то, что Дал в анонсе сделки хвалил отдельно: «live environment design … одновременно делает настройку доступа к внешним ресурсам проще и безопаснее» — потому что код не тащет секреты в себе и не выбирает ресурсы сам, а получает их извне.

3. Что это значит для объёма работ

Перенос — это не sed -i 's/Deno.env/env./'. Это три разнородных задачи:

  1. Переписать точку входа с императивного Deno.serve(...) на декларативный экспорт { fetch, scheduled } (Часть II).
  2. Перенести конфигурацию ресурсов из кода/флагов запуска в wrangler.toml (Часть III).
  3. Разобраться с зависимостями: URL- и npm:-импорты, Node-API (Часть IV).

И отдельно — то, что не имеет прямого аналога: cron и вебсокеты (Часть V). Если вы понимаете эту трёхслойную разбивку, объём миграции перестаёт быть сюрпризом.

4. Таблица «едет 1-в-1 / переписывается»

Слой DenoВ WorkersСтепень
Request, Response, request.url/method/headersте же типы Fetch APIпочти без правок
Deno.serve((req) => ...)export default { fetch(req, env, ctx) {...} }переписать точку входа
Deno.env.get("X") (не-секрет)[vars] → env.Xправка + конфиг
секрет из envwrangler secret / [secrets] → env.Xправка + конфиг (без секрета в [vars])
import ... from "https://..."бандлер решает на деплое (wrangler + esbuild)переписать импорты на npm-пакеты
import ... from "npm:pkg"pkg из package.json + флаг nodejs_compatправка + конфиг
Deno.kv[[kv_namespaces]] → env.MY_KVAPI похож, но не идентичен
Deno Deploy static / раздача файлов[assets] directory="./dist"убрать код раздачи
Deno Deploy cron[triggers] crons=[...] → метод scheduledвыражение то же, хендлер другой
Deno.upgradeWebSocketWebSocket в Durable Object (WebSocketPair)переписать на DO

Дальше по каждой строке — примеры.


Часть II. Точка входа: с Deno.serve на export default { fetch }

1. До: императивный сервер

Типичное небольшое приложение на Deno:

// main.ts — ДО (Deno)
Deno.serve((request: Request): Response => {
  const url = new URL(request.url);

  if (url.pathname === "/api/health") {
    return Response.json({ ok: true });
  }

  if (url.pathname.startsWith("/api/users/")) {
    const id = url.pathname.split("/").pop();
    const token = Deno.env.get("API_TOKEN"); // читает окружение сам
    // ... обращение к данным ...
    return new Response(JSON.stringify({ id }), {
      status: 200,
      headers: { "content-type": "application/json" },
    });
  }

  return new Response("not found", { status: 404 });
});

Обратите внимание: здесь есть и приём запроса, и маршрутизация, и доступ к окружению — всё в одном процессе, всё напрямую.

2. После: экспорт хендлеров

В Workers вы не запускаете сервер — сервер это платформа. Вы экспортируете объект с методом fetch:

// src/index.ts — ПОСЛЕ (Workers)
export interface Env {
  API_TOKEN: string;   // секрет или var — придут в env, см. Часть III
  MY_KV: KVNamespace;  // биндинг KV — материализуется из wrangler.toml
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/api/health") {
      return Response.json({ ok: true });
    }

    if (url.pathname.startsWith("/api/users/")) {
      const id = url.pathname.split("/").pop();
      const token = env.API_TOKEN;        // НЕ Deno.env.get — ресурс присвоен в env
      // ... обращение к данным через env.MY_KV ...
      return new Response(JSON.stringify({ id }), {
        status: 200,
        headers: { "content-type": "application/json" },
      });
    }

    return new Response("not found", { status: 404 });
  },
};

Что изменилось и что нет:

  • Не изменилось тело маршрутизации и конструирования ответов: new URL(request.url), request.pathname-логика, new Response(...), Response.json(...) — тот же стандартный Fetch API. Это и есть половина переносимого кода.
  • Изменилась сигнатура: вместо лямбды в Deno.serve — метод fetch(request, env, ctx). Все три аргумента даёт платформа. env — новый «карман» ресурсов, ctx — контекст (через ctx.waitUntil(promise) можно не ждать фоновой работы перед ответом).
  • Изменился доступ к окружению: Deno.env.get("API_TOKEN") → env.API_TOKEN. Значение живёт не в коде, а в конфиге/секретах (Часть III).
  • async стал нормой: рантайм воркеров рассчитывает на Promise<Response>, поэтому метод помечают async даже когда внутри пока нет await (потом await появится — для KV/БД).

3. Маршрутизация: в коде или в конфиге

В Deno Deploy часто один serve-хендлер и развилки по pathname. В Workers схема та же, но есть второй, декларативный уровень: вы можете вообще не писать if-по-путям, а навесить несколько воркеров на разные пути через [[routes]] (см. Часть III) и не трогать код. Правило большого пальца для миграции: маршрутизацию, которая у вас уже в коде, оставьте в коде — это переносится один-в-один; в [[routes]] переходите, когда захотите развести статику и API. Там же [assets] run_worker_first решает, какие пути идут в воркер, а какие отдаются как файлы.

4. Частая ошибка точки входа

Забытый default export или случайный export const handler = ... (стиль AWS Lambda) — воркер задеплоится и будет отдавать ошибку вызова. Проверяйте на wrangler dev, а не «глазком»: локальный домен эмулирует платформу и поймает это до прода.


Часть III. Конфигурация: wrangler.toml — то место, где переезжает инфраструктура

В Deno Deploy конфигурация размазана: часть в флагах запуска, часть в дашборде проекта, часть в самом коде (Deno.env, глобальный Deno.kv). В Workers best practice — считать wrangler.toml источником правды (формулировка Cloudflare: «treat Wrangler’s configuration file as the source of truth»). Ниже — ключевые секции для типового переезда. Синтаксис взят из официальной страницы конфигурации Wrangler (2026-10-29); имена ключей точные, значения-плейсхолдеры условные.

1. Каркас

# wrangler.toml
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-worker"              # [a-z0-9-], без подчёркивания
main = "src/index.ts"           # путь к entry-модулю, который будет исполняться
compatibility_date = "2026-11-06"   # версия рантайма; ставят на дату деплоя
account_id = "<YOUR_ACCOUNT_ID>"

compatibility_date — не косметика: Workers меняют поведение по датам, и это фиксирует, какой «срез» рантайма вам обещан. Ставьте актуальную на деплой дату и не откатывайте назад без причины.

2. Переменные и секреты: [vars] против зашифрованного биндинга

Открытые (не-секретные) значения объявляются прямо в конфиге — это замена нечувствительным Deno.env:

[vars]
API_HOST = "api.example.test"
FEATURE_X = "on"

Они попадут в env.API_HOST, env.FEATURE_X. Но секреты так не делают — [vars] хранит plain-текст. Секрет — это отдельный тип биндинга с зашифрованным значением (на странице конфигурации прямо разделено: «vars — map of environment variables», «Secrets — a type of binding that allow you to attach encrypted text values»). Два рабочих способа завести секрет, и оба вне [vars]:

  • для локальной разработки — файл .dev.vars в корне проекта (dotenv-формат):# .dev.vars — не коммитить! API_TOKEN="local-secret-do-not-commit"
  • для прода — значение вне репозитория: декларативно объявить, что секрет нужен (и тогда деплой его проверит), и задать командой/дашбордом:[secrets] required = [ "API_TOKEN" ]Это объявление имени, а не значение. Само значение секрета в код/конфиг не попадает — его прикладывает `wrangler secret` (семейство команд `secret` со страницей команды `wrangler secret delete <key>`; точный набор подкоманд берите со страницы команд Wrangler — на странице конфигурации он не перечислен дословно) или через дашборд Cloudflare.

В коде и то, и другое читается одинаково: env.API_TOKEN, env.API_HOST. Для мигрирующего с Deno это чистая победа: токен больше не зашит в процесс, а приходит снаружи.

3. Хранилища: KV, D1, R2 — как биндинги

Workers KV (ключ-значение, ближний к Deno.kv):

[[kv_namespaces]]
binding = "MY_KV"
id = "<NAMESPACE_ID>"
# preview_id = "<STAGING_ID>"   # для wrangler dev --remote

В коде — env.MY_KV.get(...), env.MY_KV.put(...).

D1 (serverless SQL, если на Deno был Deno SQLite / внешняя Postgres-подобная БД):

[[d1_databases]]
binding = "MY_DB"
database_name = "app-db"
database_id = "<DATABASE_ID>"
# preview_database_id = "<STAGING_DB_ID>"
migrations_dir = "migrations"

В коде — env.MY_DB.prepare(...).bind(...).all(). Схема миграций переносится папкой SQL (поле migrations_dir); тут важно — D1 это не Postgres и не ваш старый движок, SQL-диалект и API другие, это отдельная работа по данным, не механический перенос.

R2 (объекты, без egress-платы — если Deno писал файлы/загрузки куда-то):

[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "uploads"

В коде — env.MY_BUCKET.put(...), .get(...).

Лайфхак автопровижининга: можно объявить биндинг без id/bucket_name — тогда wrangler deploy создаст ресурс сам и впишет id обратно в конфиг. Удобно при первом переезде, чтобы не гонять дашборд руками. (На странице: автосоздание работает для KV, R2, D1 и др.)

4. Статика: [assets] вместо ручной раздачи файлов

Если на Deno вы сами читали файлы и отдавали их (или relied на фреймворковый статик-сервер), в Workers это declaratively:

[assets]
directory = "./dist"      # папка собранных статик-ресурсов
binding = "ASSETS"
not_found_handling = "404-page"

Ключевое поле при смешанном приложении — run_worker_first: какие пути идут в воркер (API), а какие отдаются как файлы:

[assets]
directory = "./dist"
binding = "ASSETS"
run_worker_first = [ "/api/*" ]   # /api/* зовёт fetch(); всё остальное — статика

Это позволяет не городить статик-логику в fetch — просто убрать её из кода и объявить здесь. Легаси [site] bucket=... (Workers Sites) помечен deprecated — на новых проектах используйте [assets].

5. Домен и триггеры

Привязка воркера к домену (замена «домена» Deno Deploy-проекта) — через [[routes]]. Свой домен, отдаваемый воркеру целиком:

[[routes]]
pattern = "app.example.test"
custom_domain = true

или путь внутри зоны, которой управляет Cloudflare:

[[routes]]
pattern = "example.test/api/*"
zone_name = "example.test"

Пока не переключили DNS — воркер доступен на поддомене *.workers.dev (workers_dev = true, значение по умолчанию, когда нет routes). Это даёт нам в Части VI способ держать старый и новый прод параллельно и переключиться позже.

Планировщик (cron) — в Части V, потому что там меняется не только конфиг, но и код хендлера.


Часть IV. Данные и зависимости: URL-import, npm: и Node-API

Это место, где миграция реально кусается, и где честнее всего сказать «один-в-один не едет».

1. Рантайм-импорты по URL исчезают

В Deno идиоматично тянуть модуль прямо по https:

// ДО — так в Deno работают
import { z } from "https://esm.sh/zod@3";

В Workers так нельзя: нет загрузки произвольного удалённого модуля в рантайме по запросу. Модули разрешаются бандлером на этапе деплоя (wrangler использует esbuild). Правильный перенос — обычные npm-зависимости:

npm install zod@3
// ПОСЛЕ — bare-спецификатор, соберётся при деплое
import { z } from "zod";

То есть часть «магии URL-import», за которую любят Deno, при переезде сознательно теряется ради собираемости: вместо обращения к сети за кодом в рантайме — фиксированный бандл, заложенный в воркер. Для воспроизводимости это хорошо, для привычки «подменил URL — поменялась библиотека» — нет.

2. npm:-модули и флаг совместимости

Deno тянет npm-пакеты префиксом npm:. В Workers вы устанавливаете их в package.json (как выше), но если пакет рассчитан на Node-API, нужен флаг совместимости:

compatibility_flags = [ "nodejs_compat_v2" ]

(именно nodejs_compat_v2 фигурирует в официальных примерах конфига — напр., для драйверов БД; набор флагов и их поведение — живые, сверяйте по доке Workers про Node-API).

3. Node-модули вроде fs, http: полифилл и алиас

Самое контринтуитивное для пришедших с Deno: у рантайма воркеров нет полноценной файловой системы и части Node-модулей — на странице конфигурации это видно косвенно: чтобы npm-пакет, зовущий fs.readFile или полагающийся на node-fetch (который «не работает на Workers, потому что опирается на неподдерживаемые Node.js-API из http/https»), не падал, модуль можно алиасить на полифилл:

[alias]
fs = "./fs-polyfill"

Практический вывод: если у вас зависимость пишет файлы через fs, это не «примет и заработает» — либо пакет переписан под Fetch/caches, либо нужен полифилл через [alias], либо функциональность (хранение файлов) переносится на R2 (env.MY_BUCKET), что обычно и есть правильный путь: вместо «записать в ФС» — «положить объект в бакет».

4. Deno KV → Workers KV: API похож, но не тот

Наивная замена Deno.kv.get("user:42") → env.MY_KV.get("user:42") кажется механической и в простых чтениях почти ею и является. Но не ждите полной идентичности: у Workers KV свои get(..., { type }) (текст/JSON/ArrayBuffer/stream), своя работа с метаданными и у list/get — пагинация курсором; а часть сценариев Deno KV (транзакции, strong-consistency) ближе к Durable Objects, а не к глобальному KV. Правило: обычный кэш/конфиг → KV; данные, где важна согласованность и локальная запись → D1 или DO (про DO — статья про self-hosting и разбор примитивов хранения в серии).


Часть V. Планировщик и вебсокеты: то, что не имеет прямого двойника

1. Cron: выражение переезжает, хендлер — нет

Хорошая новость: cron-выражение менять не надо — и Deno Deploy, и Workers Cron Triggers трактуют время в UTC, выражение из старой записи переносится как есть (это отмечает и независимое руководство по переезду: «cron-выражение можно перенести без изменений»). Плохая — точка входа другая.

# wrangler.toml
[triggers]
crons = [ "0 */6 * * *" ]   # каждые 6 часов, UTC — как и было на Deno

На Deno у cron-кода был свой callback; в Workers вы добавляете метод scheduled рядом с fetch в тот же экспорт:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // ... HTTP-трафик ...
  },

  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    // фоновое задание по cron; await делать нельзя надолго — длинное в ctx.waitUntil(...)
    const res = await env.MY_KV.put("last-run", new Date().toISOString());
    // ... агрегация, выборки, отправка ...
  },
};

Разница модели: cron воркера должен быть быстрым и не «висеть» — долгую работу оформляют через ctx.waitUntil() или, если она большая, через Queues (продакшн-очереди у Workers — свой биндинг; это смежно с разбором очередей сообщений). Дисконнект: если у вас на Deno был долгий cron, не тащите его «как есть» — разбейте на постановку в очередь.

2. Вебсокеты: upgradeWebSocket → Durable Object

Это самая непереносимая часть. На Deno вы апгрейдили соединение напрямую:

// ДО (Deno)
const { socket, response } = Deno.upgradeWebSocket(request);
socket.onmessage = (e) => { /* ... */ };
return response;

У Workers нет stateful-глобального сокета внутри fetch: инстанс хендлера одноразовый. Долгоживущее соединение с памятью — это Durable Object («адресуемый маленький сервер с базой», тот самый примитив из первой статьи). Схема: воркер в fetch создаёт/находит DO и передаёт ему сокет через WebSocketPair, а сам DO держит обработчики сообщений:

// ПОСЛЕ (Workers): stateful-сокет живёт в Durable Object
import { WebSocketPair } from "cloudflare:sockets";

export class ChatDO extends DurableObject {
  async fetch(request: Request) {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);
    this.ctx.accept(server);                 // DO владеет живым сокетом
    server.addEventListener("message", (ev) => { /*broadcast/состояние*/ });
    return new Response(null, { status: 101, webSocket: client });
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.CHAT.idFromName("room-" + new URL(request.url).pathname);
    const stub = env.CHAT.get(id);           // адресация DO по имени комнаты
    return stub.fetch(request);              // отдаём сокет-апгрейд конкретному DO
  },
};

CHAT — биндинг Durable Object:

[[durable_objects.bindings]]
name = "CHAT"
class_name = "ChatDO"

[exports.ChatDO]
type = "durable-object"
storage = "sqlite"

Вывод из этого один: если ваше Deno-приложение держит реалтайм-сокеты, перенос — не про «скопировать хендлер», а про перестройку на DO (шардирование состояния по «комнатам/сессиям»). Это ровно тот случай, ради которого затеян весь переезд на модель Workers, но работа тут отдельная и крупная. Глубже про DO и про то, как он работает и на чужом железе — в статье про self-host workerd/celld; для SSE вместо WS как более простой переносимый вариант реалтайма — потоки в браузере.


Часть VI. Порядок переезда: от wrangler dev до переключения домена

Собираем в runbook, который можно выполнить без простоя и с откатом.

1. Подготовка каркаса

npm create cloudflare@latest my-worker -- --type=hello-world   # или вручную + wrangler
cd my-worker
npm install                                                   # зависимости из package.json

Переносите код: entry на export default { fetch, scheduled } (Части II/V), зависимости с URL- и npm:-импортов на package.json (Часть IV), конфиг wrangler.toml с биндингами/[vars]/[secrets] (Часть III). Секреты локально кладите в .dev.vars и не коммитьте (.gitignore).

2. Локальный тест против эмуляции платформы

npx wrangler dev        # локально, ресурсы эмулируются; секреты берутся из .dev.vars

Здесь ловится то, что «глазком» не видно: отсутствие default export, неверные имена биндингов (env.MY_KV должно совпадать со binding = "MY_KV"), падение npm-пакета на fs (заведите полифилл или несите на R2), несовпадение compatibility_date/флагов. Прогоните все основные пути приложения, а не только «здравствуй, мир».

3. Параллельный прод на workers.dev, старый ещё жив

npx wrangler secret put API_TOKEN     # завести секрет на прод (значение не в конфиге)
npx wrangler deploy

После деплоя воркер доступен на my-worker.<аккаунт>.workers.dev (при workers_dev по умолчанию). Не переключайте домен сразу. Держите Deno Deploy работающим, а Workers — задеплоенным на поддомен, и гоняйте туда трафик для сверки: ответ за ответом сравните поведение, замеры, ошибки. Пока дедлайн закрытия Deno Deploy далеко (≈весна 2027), это безопасное окно двойной работы. Для сверки ответов подключите логирование — observability/logpush в конфиге; саму идею «снимать фактическое поведение, а не надеяться» разбирает наш паттерн cron→снимок→рендер.

4. Ключи БД и миграции данных

Если на Deno было состояние (KV/SQLite), решите до переключения: переносить значения или строить заново. wrangler d1/KV позволяют загнать данные; для KV есть экспорт/импорт значений. Проверьте диалект D1 заранее — SQL с вашего старого движка переносится не автоматически. Нагрузка на перенос данных зависит от объёма; для больших файлов — R2, а не «прогон через воркер» (лимиты на размер тела и время запроса — в доке Workers про лимиты, сверяйте).

5. Переключение домена (cutover)

Когда поведение на workers.dev совпало, объявляете маршрут:

[[routes]]
pattern = "app.example.test"
custom_domain = true
npx wrangler deploy

Домен app.example.test начинает обслуживаться воркером. Deno Deploy ещё живёт — это ваш откат: если на Workers что-то сломалось, снимаете [routes]/custom_domain и возвращаете DNS/сервис на старый источник. Именно поэтому старый прод не гасят в день переключения, а держат до подтверждения.

6. После переключения

  • Уберите из кода всё, что больше не нужно (ручная раздача статики, кастомные env-читалки).
  • Переведите preview_id/staging-окружения ([env.staging]) — биндинги не наследуются между окружениями, для стейджа объявляйте их отдельно.
  • Закройте секретное: убедитесь, что ни в [vars], ни в репозитории нет токенов (см. гигиену ключей в защите API).
  • Поставьте наблюдение: ошибки на воркере (observability.issues.enabled = true) и логи, чтобы увидеть продакшн-поведение, а не только локальный wrangler dev.

7. Карта «где переезд будет долгим»

  • Тонкий API на Fetch без внешних тянет → быстрые минуты, почти 1-в-1.
  • Много URL-/npm:-импортов → часы на сборку и полифиллы.
  • Своё состояние с гарантиями (не просто кэш) → дни, это перестройка на D1/DO.
  • Реалтайм-вебсокеты → самая длинная часть, переписывается на Durable Objects (Часть V).

Честно: «перенести за вечер» реально только для первой строки. И это нормальный вывод миграции: ценность в том, что после переезда вы в модели, которую вендор открыл и на своё железо тоже, — но платёж за это в части реалтайма и состояния настоящий, и его надо закладывать в план работ, а не в «sed по коду».


Инструменты под задачу

  • Разобрать конкретный worker и найти, что ломается — AI-ассистент для кода на codestack.space: вставьте свой Deno.serve-фрагмент вместе с wrangler.toml и попросите показать, какие вызовы Deno.* не имеют аналога и как переложить их на env/биндинги.

Чек-лист граблей

  1. Искать «переименовывалку» Deno. → env.. Этого мало: меняется точка входа, конфиг и модель зависимостей. Ожидать один sed — главный способ недооценить работу.
  2. Секрет в [vars]. [vars] — plain-текст, он не для токенов. Секрет — отдельный зашифрованный биндинг (.dev.vars локально, wrangler secret/дашборд на проде); значение никогда не коммитится.
  3. Рассчитывать, что URL-import заработает. В Workers удалённый модуль по https в рантайме не грузится; зависимости бандлятся из package.json на деплое.
  4. Тащить Node-пакет, пишущий в fs. У воркеров нет полноценной ФС; либо полифилл через [alias], либо — правильно — перенос файловых вещей на R2.
  5. Ждать идентичности Deno KV и Workers KV. Похожий, но не тот API; strong-consistency/транзакции — это D1/DO, а не глобальный KV.
  6. Переносить вебсокеты «как есть». Deno.upgradeWebSocket прямого двойника нет — это перестройка на Durable Object с WebSocketPair. Не для одного вечера.
  7. Гасить Deno Deploy в день переключения домена. Старый прод — ваш откат; держите его до подтверждения паритета на workers.dev.
  8. Ставить неверную compatibility_date/флаги. Они определяют срез поведения рантайма; ставьте актуальную дату деплоя и nodejs_compat_v2, когда тянет Node-API.
  9. Надеяться на автоматический перенос SQL в D1. D1 — не ваш старый движок; диалект и миграции проверяйте заранее, migrations_dir не отменяет пересмотр запросов.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.