LAND OF FIRE
РАЗБОР ПРОЕКТА
005 // LAND OF FIRE

Веб-сайт ресторана и Система бронирования на Edge

Следующий.js 16Реагировать 19TypeScriptPostgreSQLРабочие CloudflareOpenNextGSAPLenisPlaywright
lof.artursdev.co.uk
КОРОТКО

Land of Fire — азербайджанский ресторан на Каледониан-роуд в Кингс-Кросс. Сайт должен был рассказать свою историю, принимать заказы на столики без стороннего виджета и предоставить сотрудникам одно личное место для работы перед домом.

РЕШЕНИЕ

Я создал кинематографический веб-сайт на трех языках, общедоступный процесс бронирования и частное рабочее пространство для сотрудников для бронирования и электронной почты для гостей, и все это работает как один Cloudflare Worker, поддерживаемый PostgreSQL.

РЕЗУЛЬТАТ

Гости запрашивают столик онлайн и получают информацию по электронной почте. Сотрудники подтверждают, изменяют и экспортируют бронирования из одного места. Здесь нет серверов, которые нужно обслуживать, а также аналитических или рекламных файлов cookie.

//История
  1. ОТПРАВНАЯ ТОЧКА

    Веб-сайт, на котором перечислена еда, но ничего не сказано о месте.

    У Land of Fire уже был веб-сайт, созданный на основе шаблона онлайн-заказа. Там было меню, адрес и часы работы. Он не показал, чем отличается ресторан: домашняя азербайджанская кухня, чай, подаваемый в стаканах армуду, и всегда место для еще одного человека за столом.

  2. ЧТО Я ЗАМЕТИЛ

    Старый сайт противоречил сам себе

    На главной странице в трех местах было написано «Заказать онлайн», при этом встроенное в страницу уведомление сообщало, что онлайн-заказ отключен, а плавающие вкладки предлагали гостям зарегистрировать для него учетную запись. Понедельник был указан как «00:00, 00:00», когда ресторан закрыт по понедельникам. Всплывающее окно с объявлением о закрытии в августе все еще отображалось в октябре. И сайт был только на английском языке, хотя гости азербайджанского ресторана с одинаковой вероятностью читают как по-азербайджански, так и по-русски. Каждая проблема была небольшой, но вместе они сказали впервые зашедшему посетителю, что за сайтом никто не присматривает.

  3. ИДЕЯ

    «Всегда есть место еще для одного»

    Я перестал относиться к сайту как к каталогу еды на вынос и превратил его в приветствие. Эта строка стала нитью через весь сайт. Он начинается с полноэкранного фильма, затем знакомит с людьми, блюдами, приготовленными для совместного использования, и стоящими за ними азербайджанскими корнями. Он заканчивается указанием направления к двери. Золотые линии, изображающие Девичью башню, чайный сервиз, ковер и смолу, придают ему индивидуальность, которую не мог бы создать шаблон.

  4. ЗАСТАВИТЬ ЭТО РАБОТЬТЬ

    Красивый сайт – это только половина дела

    Ресторан сам подтверждает каждый столик, поэтому форма бронирования обещает запрос, а не мгновенное подтверждение, и говорит об этом прямо. За ним я создал частное рабочее пространство, где сотрудники подтверждают, изменяют и экспортируют бронирования, а также отвечают на электронные письма гостей из одного места. Приведенная ниже технология существует для того, чтобы выполнить это единственное обещание: запрос никогда не теряется, никогда не дублируется и никогда не утекает.

  5. СЕГОДНЯ

    Одно место, где можно найти, принять решение и забронировать

    Теперь гость может прочитать историю, просмотреть полное меню с ценами, проверить отзывы и запросить столик на одной странице на английском, азербайджанском или русском языке. Сотрудники работают с единой панели управления вместо почтового ящика и телефона. Нет серверов, которые нужно обслуживать.

//Дизайн: До и После
ПОЛНАЯ ГЛАВНАЯ СТРАНИЦА // ПЕРЕТАЧИТЕ ⇆, ЧТОБЫ СРАВНИТЬ, ПРОРУСТИТЕ ↓, ЧТОБЫ ИЗУЧИТЬ
ВИД:
The full redesigned Land of Fire homepage on desktop: restaurant film, welcome, dishes made to share, Azerbaijani heritage, tabbed menu, reviews, booking form, directions and illustrated footer
The full original Land of Fire homepage on desktop: photo slider, category tiles, a long About paragraph, booking form, app promotion, two identical hours tables, newsletter and footer

ОРИГИНАЛ СТРАНИЦА ЗАКОНЧАЕТСЯ ЗДЕСЬ

ОРИГИНАЛРЕДИЗАЙН
⇆
НАВИГАЦИЯ // МЕНЮ ОТКРЫТЬ
ВИД:
Redesigned navigation: a full-screen burgundy overlay with five large numbered links (Our story, Azerbaijan, The menu, Find us, Book a table) beside address, phone and social links
Original desktop navigation: contact bar, social icons, Reservation button and a Menu dropdown listing 12 food categories
ОРИГИНАЛРЕДИЗАЙН
⇆
ОРИГИНАЛЬНАЯ НАВИГАЦИЯ
  • —Верхняя панель с электронной почтой, телефоном, четырьмя значками социальных сетей и кнопкой «Резервировать» над вторым рядом ссылок.
  • —«Меню» открывает раскрывающийся список из 12 категорий продуктов питания, каждая из которых имеет отдельную страницу.
  • —Вкладки входа и регистрации расположены на левом краю каждой страницы для учетной записи для заказа.
  • —На телефонах: небольшая панель с четырьмя ссылками, плюс фиксированная панель из пяти кнопок, включая «Заказать онлайн» для отключенной системы заказа.
ОБНОВЛЕННАЯ НАВИГАЦИЯ
  • —Четыре тихие текстовые ссылки и логотип по центру; ничто не может конкурировать с фильмом
  • —Одна кнопка «Меню» открывает полноэкранное изображение с пятью пронумерованными пунктами назначения, заканчивающееся надписью «Забронировать столик».
  • —Адрес, телефон, Deliveroo, электронная почта и ссылки на социальные сети расположены рядом с меню, поэтому контактные данные находятся на расстоянии одного касания.
  • —Одно и то же наложение на экране любого размера, открывается и закрывается как с помощью клавиатуры, так и касанием.

Первое впечатление

ОРИГИНАЛЬНЫЙ САЙТ

Общий текст «Добро пожаловать на официальный сайт» и четыре ссылки на изображения без надписей.

РЕДИЗАЙН

Полноэкранный фильм о ресторане «Немного огня. Много души». и единственная кнопка «Забронировать столик»

Рассказывание историй

ОРИГИНАЛЬНЫЙ САЙТ

Никакой истории, наследия или людей; один абзац шаблона

РЕДИЗАЙН

Главы «Наша история», «За нашим столом» и «Наши корни» с карточками наследия, связанными с источниками ЮНЕСКО.

Визуальная идентичность

ОРИГИНАЛЬНЫЙ САЙТ

Готовый шаблон заказа с плитками категорий запасов

РЕДИЗАЙН

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

Меню

ОРИГИНАЛЬНЫЙ САЙТ

Двенадцать отдельных страниц категорий, каждая из которых загружается одним щелчком мыши.

РЕДИЗАЙН

Одно меню с вкладками с описаниями блюд и ценами, а также загрузкой PDF-файла и ссылкой на Deliveroo.

Бронирование

ОРИГИНАЛЬНЫЙ САЙТ

Форма, отображаемая рядом с кнопкой «заказать сейчас» для отключенной системы заказа; Призыв к бронированию повторяется около пяти раз

РЕДИЗАЙН

Одна понятная форма с реальными часами работы, набором от 1 до 8 гостей, согласием и честной формулировкой «просьба, а не подтверждение».

Часы работы

ОРИГИНАЛЬНЫЙ САЙТ

Понедельник отображается как «00:00, 00:00»; блок часов дублируется как «Часы бронирования»

РЕДИЗАЙН

«Понедельник: закрыто», указывается один раз и применяется тем же модулем, который использует API бронирования.

Языки

ОРИГИНАЛЬНЫЙ САЙТ

только английский; переключатель EN/DE присутствует в коде, но скрыт от посетителей

РЕДИЗАЙН

Английский, азербайджанский и русский, с альтернативами hreflang для поисковых систем.

Доверие и социальное доказательство

ОРИГИНАЛЬНЫЙ САЙТ

Всплывающее окно, объявляющее о закрытии в августе, все еще отображается в октябре, заполнители «Загрузка…» и опечатки в тексте.

РЕДИЗАЙН

Цитаты Tripadvisor над формой бронирования, ссылки на Google и Tripadvisor, неработающих ссылок нет.

Доступность

ОРИГИНАЛЬНЫЙ САЙТ

Ссылки только на изображения без альтернативного текста

РЕДИЗАЙН

Пропустить ссылку, семантические ориентиры, средства выбора с помощью клавиатуры и полную поддержку ограниченного движения.

ОРИГИНАЛ: LANDOFFIRERESTAURANT.COM
Пользовательский интерфейс для живого производства // ШЕЙДЕР СМЕЩЕНИЯ: АКТИВНЫЙ
lof.artursdev.co.uk
LAND OF FIRE главный экран сайта
┌─[КРАТКОЕ РЕЗЮМЕ]───────────────────────┐

Land of Fire — это полноценная ресторанная веб-платформа: многоязычный маркетинговый сайт, публичный процесс бронирования и частное рабочее пространство для персонала, созданное с помощью Следующий.js 16 и PostgreSQL и развернут как один Облачный работник.

Публичные страницы предварительно визуализируются и представляют собой статический контент; только API бронирования и персонала выполняют код по каждому запросу. Корректность обеспечивается базой данных: ключи идемпотентности, консультативные блокировки, блокировки строк, оптимистические версии и транзакционная исходящая электронная почта, с интеграционными тестами на реальных почтовых серверах PostgreSQL и синтетических TLS.

└──────────────────────────────────────────┘
РОЛЬЕдинственный разработчик
ТИППолнофункциональное веб-приложение
РАМКИNext.js 16/React 19/TypeScript
БАЗА ДАННЫХPostgreSQL через postgres.js
ВРЕМЯ РАБОТЫРабочие Cloudflare через OpenNext
РАЗМЕР~12 800 строк, 9 страниц, 8 маршрутов API
//Ключевые факты и показатели
1
Развертываемый рабочий
Нет очереди, уровня кэша или cron
~20 мс
Рабочий стартап
Пакет размером ~2,3 МБ в формате gzip
3
Языки
английский, азербайджанский, русский
0
Сторонние трекеры
Никакой аналитики, рекламных файлов cookie и виджетов.
//Ограничения
01
ОГРАНИЧЕНИЕ 01

Нет серверов, низкие эксплуатационные расходы

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

02
ОГРАНИЧЕНИЕ 02

Гостевые данные являются личными данными

Имена, электронные письма, номера телефонов и заметки о питании никогда не должны просачиваться на общедоступные страницы, кэши, журналы или создавать артефакты.

03
ОГРАНИЧЕНИЕ 03

Каждое бронирование подтверждено вручную

Ресторан сам подтверждает каждое бронирование. Система записывает запросы и информирует людей; он не распределяет таблицы и не выполняет автоматическое подтверждение.

04
ОГРАНИЧЕНИЕ 04

Ненадежная сеть

Люди бронируют билеты на телефонах, в поездах и дважды нажимают кнопку «Отправить». Повторная попытка или повторный запрос никогда не должны создавать второе бронирование.

//Архитектура системы
[КЛИЕНТЫ]
Гостевой браузерСтраницы + форма бронирования
Персональный браузер/reservations workspace
Гостевые входящиеПодтверждения + приглашения
↓
[КРАЙ ОБЛАКА]
Статические активыHTML, CSS, JS, видео, PDF
РабочийNext.js через OpenNext
Секретные привязкиЗашифрованный, не в комплекте
↓
[ДАННЫЕ]
PostgreSQLTCP-сокет на запрос
Исходящие сообщения электронной почтыТа же транзакция, что и при бронировании
События аудитаКаждая смена персонала
↓
[ПОЧТА]
SMTPSTARTTLS / TLS
Почтовый ящик IMAPВходящие только для чтения
Календарь приглашений.ics + Google link
//Процесс бронирования
  1. 01

    Запрос

    Браузер генерирует UUID, когда гость впервые нажимает кнопку «Отправить», и повторно использует его при каждой повторной попытке.

  2. 02

    Сторожить

    Проверка того же происхождения, тип контента JSON, ограничение размера тела 8 КБ, скрытое поле-приманка и проверка на соответствие реальным часам работы.

  3. 03

    Замок

    Консультативная блокировка PostgreSQL на уровне транзакции, введенная в электронную почту гостя, сериализует одновременные запросы между экземплярами Worker.

  4. 04

    Писать

    Бронирование, событие аудита и электронные письма гостей и персонала вставляются в одну транзакцию. Вместо этого при повторной попытке возвращается исходная ссылка.

  5. 05

    Доставлять

    После фиксации Worker отправляет поставленные в очередь электронные письма в максимально возможном порядке и отвечает 201 со ссылкой на бронирование.

//Инженерные действия
01 /Идемпотентные бронирования без расовой принадлежности
Ключ идемпотентности как первичный ключ

UUID, сгенерированный клиентом, является первичным ключом бронирования, поэтому двойное касание или повторная попытка после тайм-аута возвращает исходную ссылку. Повторная попытка, поступившая с другого адреса электронной почты, отклоняется, поэтому предполагаемый идентификатор не может проверить чужое бронирование.

Один источник информации о часах работы

Один модуль определяет часы работы и импортируется как через форму, так и через API: выходной по понедельникам, получасовые интервалы, последнее посещение за два часа до закрытия, 1–8 гостей, до 90 дней вперед, всегда оценивается по лондонскому времени.

Изящная деградация

Если база данных недоступна, форма отображает номер телефона ресторана, а не выдает ошибку. Публичный сайт никогда не зависит от базы данных.

02 /Надежная электронная почта: транзакционная исходящая почта
Спасены вместе или не спасены вообще

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

Безопасная одновременная отправка

Ожидающие сообщения запрашиваются по одному с надписью FOR UPDATE SKIP LOCKED. Сбои возвращаются в состояние ожидания, и сообщение, ожидающее отправки более пяти минут, снова становится доступным для запроса.

Точные повторы и правильные приглашения

Обработанный HTML, обычный текст и вложение .ics сохраняются в строке, поэтому при повторной попытке отправляется именно то, что было составлено. Время преобразуется из лондонских настенных часов в UTC, поэтому приглашения остаются верными даже при переходе на летнее время.

03 /Аутентификация и авторизация персонала
Одноразовые коды, отправленные по электронной почте

Шестизначные коды действительны в течение десяти минут, одноразовые, с максимум пятью попытками угадать внутри замка строки. В базе данных хранятся только дайджесты SHA-256 токена и кода вызова.

Пароли и сеансы

Argon2id в формате PHC, со старыми хеш-кодами, обновляемыми при следующем успешном входе в систему и пятнадцатиминутной блокировкой после пяти неудач. Сеансы представляют собой 32 случайных байта в файле cookie HttpOnly, Secure, SameSite=Strict; сохраняется только дайджест.

Одна оболочка для каждой конечной точки персонала

Одна оболочка обеспечивает проверку происхождения при записи, проверяет сеанс, устанавливает Cache-Control: частный, без хранения, очищает журнал ошибок и всегда закрывает соединение с базой данных. Все кадровые изменения записываются в таблицу аудита.

04 /Параллельное редактирование и безопасный экспорт
Оптимистический параллелизм

Каждое бронирование имеет номер редакции. Сервер блокирует строку, сравнивает и возвращает 409 Conflict, если коллега сохранил запись первым, поэтому два планшета не могут незаметно перезаписать друг друга.

CSV-файл, безопасный для формул

Ячейки, начинающиеся с =, +, - или @, имеют префикс, поэтому программное обеспечение для работы с электронными таблицами обрабатывает их как текст. Заметки, предоставленные гостем, не могут выполняться как формулы, если экспорт открыт в Excel.

Входящие только для чтения

Почтовый ящик IMAP открывается только для чтения, флаги непрочитанного остаются нетронутыми и отображается только обычный текст. Удаленный HTML-код и изображения никогда не отображаются на панели управления для персонала.

05 /Запуск на рабочих процессах Cloudflare
Нет пула подключений

В Workers TCP-сокет принадлежит запросу, который его открыл. Каждый вызов API открывает ровно одно соединение с отключенными подготовленными операторами и закрывает его в блокеfinally.

Правильный драйвер, правильное имя хоста

Помечен postgres.js как внешний, поэтому OpenNext выбирает сборку рабочего файла, при этом на этапе сборки утверждается, что драйвер сокета Cloudflare находится на выходе. Исправлен SMTP STARTTLS путем передачи nodemailer сокета, подключенного по имени хоста, при этом проверка сертификата оставалась полностью включенной.

Исправление адаптера

Next.js 16.4 генерирует манифест, который не был включен в OpenNext 1.20, что нарушает работу Worker во время выполнения. Отследил его до одного шаблона glob и добавил однострочный патч, автоматически применяемый при установке.

06 /Сборка, которая отказывается раскрывать секреты
Раздеть, затем отсканировать

Сценарий после сборки перезаписывает модуль среды адаптера, чтобы сохранить только значения NEXT_PUBLIC_, затем сканирует каждый выходной файл на предмет буквального URL-адреса базы данных и почтовых паролей, а также на наличие случайных файлов .env. Развертывание завершается неудачей, если что-то найдено.

Зашифрованные рабочие секреты

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

Развертывание одной командой

Одна команда создает приложение, преобразует его с помощью OpenNext, запускает секретное сканирование и развертывает с помощью Wrangler. 223 статических ресурса загружаются постепенно.

07 /Фронтэнд-крафт и доступность
Движение

Введение в временную шкалу GSAP, встроенные сцены SVG, анимированные с помощью ScrollTrigger и плавной прокрутки Lenis, координируемые с помощью пользовательских событий DOM. Меню выбора даты и выбора созданы с нуля с поддержкой клавиатуры.

Уважение к посетителю

Все функции анимации предпочитают уменьшенное движение: вступление пропускается, главное видео приостанавливается, а эффекты прокрутки отключаются. Пропуск ссылок, семантические ориентиры, атрибуты языка для каждого языка и видимый элемент управления паузой видео.

SEO и интернационализация

Метаданные на каждой странице от одного помощника, обратный hreflang чередуется с x-default, сгенерированной картой сайта и файлами robots, а также Restaurant JSON-LD. В структурированных данных не заявлены ни звезды обзора, ни совокупные рейтинги.

08 /Тестирование на реальных сервисах
Интеграция через макеты

Тесты резервирования запускают одноразовый кластер PostgreSQL и рабочий сервер, затем проверяют постоянство, идемпотентность, одновременные повторные попытки, проверку, согласие, защиту источника и ограничения скорости.

Сквозные рабочие процессы персонала

Миграция схемы из исходной версии, вход в систему и сеансы, конфликты редактирования, экспорт CSV и доставка электронной почты, сбои и повторные попытки на синтетических почтовых серверах TLS на настольных компьютерах и мобильных устройствах.

Дымовой тест после развертывания

Каждый маршрут, иллюстрации, загрузка PDF-файла с точностью до байта, запросы видеодиапазона, подтверждение бронирования и защита происхождения. Ни один тест не затрагивает реальную базу данных ресторана и не отправляет настоящую электронную почту.

//Компромиссы и следующие шаги

Персонал подтверждает бронирование вручную

РАСХОДЫ

Нет живой доступности; возможно двойное бронирование

СЛЕДУЮЩИЙ ШАГ

Инвентаризация таблиц и проверка емкости на слот

Повторные попытки электронной почты инициируются запросами или персоналом.

РАСХОДЫ

Неудачное электронное письмо ожидает следующего действия или повторной попытки вручную.

СЛЕДУЮЩИЙ ШАГ

Триггер Cloudflare Cron опустошает исходящие сообщения

Одно подключение к базе данных на каждый запрос

РАСХОДЫ

Задержка установки соединения при каждом вызове API

СЛЕДУЮЩИЙ ШАГ

Cloudflare Hyperdrive для периферийного пула

Ограничение скорости вводится только по электронной почте

РАСХОДЫ

Скрипт может вращать адреса

СЛЕДУЮЩИЙ ШАГ

Ограничения на основе IP или вызов турникета

Форма бронирования и внутренние страницы только на английском языке.

РАСХОДЫ

Книга посетителей Азербайджана и России на английском языке

СЛЕДУЮЩИЙ ШАГ

Переведите процесс бронирования и меню

Аутентификация встроенная

РАСХОДЫ

Больше кода для владения и аудита

СЛЕДУЮЩИЙ ШАГ

Приемлемо в этом объеме; повторно посетите, если требуется SSO или MFA

ПРОИЗВОДСТВО // ЖИТЬ
Посмотреть действующий сайт по адресу lof.artursdev.co.uk.
lof.artursdev.co.uk
Узнать стоимость

Есть идея проекта?

Расскажи мне, чего ты пытаешься достичь. Вам не нужно знать, какая технология вам нужна: я помогу определить технический подход, объем, сроки и стоимость до начала разработки.

Откроется ваше почтовое приложение с заполненными данными. Ничего не будет отправлено, пока вы не нажмете «Отправить».