Я сделал онлайн-блокнот без регистрации и сервера. Вот почему хранить всё в браузере оказалось сложнее, чем я думал

Идея казалась предельно простой: пользователь открывает сайт и сразу начинает писать.

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

Заметки должны храниться непосредственно в браузере пользователя. Сервер их не получает, а значит, не читает, не анализирует и не теряет из-за очередного сбоя базы данных.

На словах это почти идеальный блокнот. На практике оказалось, что фраза «просто сохраним всё в браузере» скрывает довольно много проблем.

Зачем делать ещё один онлайн-блокнот

Онлайн-блокнотов уже достаточно. Есть большие сервисы с синхронизацией, совместной работой, базами данных, календарями и искусственным интеллектом. Есть минималистичные редакторы. Есть обычный системный «Блокнот».

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

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

Мне хотелось сделать инструмент, который работает иначе:

  • открыл страницу;
  • создал заметку;
  • написал текст;
  • закрыл страницу;
  • позже вернулся и продолжил работу.
Создание новой заметки в онлайн-блокноте Textria
Создание новой заметки в онлайн-блокноте Textria

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

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

Общий вид браузерного блокнота Textria
Общий вид браузерного блокнота Textria

На первом этапе я думал, что самая сложная часть проекта — редактор. Оказалось, что текстовое поле сделать относительно легко. Намного сложнее гарантировать, что написанный в нём текст не исчезнет.

«Данные хранятся в браузере» — звучит понятнее, чем работает

У браузера есть несколько вариантов локального хранения данных.

Самый известный — localStorage. Он простой: записал строку по ключу, потом прочитал её. Для темы интерфейса или небольшой настройки этого достаточно.

Но полноценный блокнот быстро перестаёт быть «небольшой настройкой». Появляются:

  • десятки и сотни заметок;
  • длинные тексты;
  • папки;
  • закреплённые записи;
  • дата создания и изменения;
  • настройки сортировки;
  • история изменений;
  • импортированные документы.
Организация заметок по папкам
Организация заметок по папкам

Хранить всё это в одном JSON внутри localStorage можно, но только до определённого момента. Любое изменение требует прочитать весь объект, изменить его и записать заново. При повреждении данных можно потерять сразу все заметки.

Поэтому основным хранилищем стала IndexedDB — встроенная в браузер база данных.

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

Но вместе с возможностями появляются и новые вопросы.

Что делать, если транзакция завершилась с ошибкой? Как обновлять структуру базы между версиями приложения? Что произойдёт, если пользователь откроет блокнот сразу в нескольких вкладках? Как не потерять текст, если вкладку закрыли во время сохранения?

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

Автосохранение — это не просто обработчик onChange

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

Наивная реализация выглядит примерно так: при каждом изменении текста отправляем новое содержимое в базу.

Но современный редактор генерирует много изменений. Если сохранять документ после каждого введённого символа, количество операций быстро становится избыточным.

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

Это решает одну проблему и создаёт несколько новых.

Например, пользователь написал предложение и сразу закрыл вкладку. Таймер сохранения ещё не успел сработать. Последние изменения могут пропасть.

Или пользователь быстро переключился между заметками. Нужно гарантировать, что содержимое первой заметки сохранится именно в неё, а не в ту, которая сейчас открыта.

Пришлось явно разделить состояния:

  • заметка изменена;
  • сохранение запланировано;
  • идёт запись в базу;
  • изменения сохранены;
  • произошла ошибка.

В интерфейсе появились статусы «Сохраняю…» и «Сохранено». Это небольшая деталь, но без неё пользователь не понимает, можно ли уже закрывать страницу.

Статус автоматического сохранения заметки
Статус автоматического сохранения заметки

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

Очистка браузера становится аналогом удаления базы данных

У локального хранения есть принципиальное ограничение: браузер принадлежит пользователю, а не приложению.

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

Для приложения это выглядит так, будто кто-то полностью удалил его базу данных.

С одной стороны, это честная модель. Данные действительно находятся под контролем пользователя.

С другой — многие воспринимают браузерный блокнот так же, как облачный сервис. Они ожидают, что заметки будут доступны после переустановки системы или входа с другого устройства.

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

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

Экспорт заметок из браузерного блокнота
Экспорт заметок из браузерного блокнота

В будущем можно добавить облачную синхронизацию, но здесь возникает противоречие: как только появляется сервер, сервис перестаёт быть полностью локальным.

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

Разные браузеры ведут себя по-разному

Когда приложение работает с обычным серверным API, большинство различий между браузерами скрыто за HTTP-запросами.

Когда приложение зависит от внутреннего хранилища браузера, эти различия становятся частью продукта.

У браузеров могут отличаться:

  • ограничения на объём данных;
  • поведение в приватном режиме;
  • правила автоматической очистки;
  • работа хранилища при нехватке места;
  • поддержка некоторых API;
  • обработка вкладок в фоне.

Особенно неприятны ошибки, которые невозможно воспроизвести у себя. У пользователя не сохранилась заметка, но неизвестно, было ли заполнено хранилище, запрещена ли запись политиками браузера или данные очистило расширение.

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

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

Несколько вкладок — уже распределённая система в миниатюре

Пользователь может открыть блокнот в двух вкладках.

В первой он редактирует заметку. Во второй открывает ту же запись и тоже начинает что-то менять.

Какая версия должна остаться?

Самый простой вариант — последняя запись побеждает. Но тогда вкладка со старым содержимым может незаметно перезаписать новый текст.

Можно блокировать редактирование во второй вкладке, синхронизировать изменения через BroadcastChannel или предупреждать пользователя о конфликте.

Для небольшого блокнота всё это звучит слишком серьёзно. Но потеря текста из-за двух вкладок будет выглядеть не как «редкий конфликт конкурентного доступа», а как обычная ошибка приложения.

Локальное приложение не избавляет от проблем синхронизации. Оно просто переносит их с нескольких серверов на несколько вкладок одного браузера.

Обновление приложения тоже может сломать старые заметки

Сначала модель заметки была простой: идентификатор, заголовок, текст и даты.

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

Старые заметки уже лежат в браузерах пользователей. Нельзя просто изменить структуру данных и ожидать, что всё продолжит работать.

Нужны миграции.

При запуске новой версии приложение должно определить версию локальной базы, обновить её структуру и при необходимости преобразовать старые записи.

Ошибка в такой миграции опаснее обычного визуального бага. Неправильный отступ можно исправить следующим обновлением. Повреждённые пользовательские данные восстановить гораздо сложнее.

Именно в этот момент начинаешь иначе относиться к выражению «production-ready». Оно означает не только отсутствие ошибок в интерфейсе, но и способность обновлять приложение, не ломая данные, которые уже существуют у пользователей.

Редактор тоже оказался отдельным проектом

Изначально можно было ограничиться обычным textarea. Для быстрых заметок этого достаточно.

Но затем появляются вполне логичные запросы:

  • заголовки;
  • списки;
  • чек-листы;
  • ссылки;
  • горячие клавиши;
  • вставка форматированного текста;
  • история отмены действий;
  • корректная работа курсора;
  • мобильная версия.

После этого редактор уже нельзя воспринимать как большое поле ввода. Он должен понимать структуру документа.

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

Это удобнее для форматирования, но усложняет импорт, экспорт и хранение.

Например, обычный TXT не поддерживает заголовки и чек-листы. Markdown поддерживает часть структуры, но его нужно корректно преобразовать в формат редактора и обратно.

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

Почему я всё равно не перенёс заметки на сервер

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

Технически — возможно.

Сервер позволяет централизованно хранить данные, делать резервные копии, синхронизировать устройства и контролировать обновления структуры.

Но у локальной модели есть преимущества, ради которых проект и создавался.

Можно начать работу сразу

Пользователь открывает приложение и пишет. Нет обязательной регистрации и стартового мастера настройки.

Сервис не получает содержимое заметок

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

Блокнот может работать без подключения к интернету

После загрузки приложения основные операции выполняются локально.

Нет зависимости от состояния серверной инфраструктуры

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

Меньше затрат на инфраструктуру

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

Цена этих преимуществ — необходимость честно объяснять ограничения и давать пользователю инструменты для резервного копирования.

Из блокнота постепенно вырос набор текстовых инструментов

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

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

Так в проекте появился раздел с инструментами.

Инструмент очистки текста в Textria
Инструмент очистки текста в Textria

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

Но здесь возникла другая проблема: легко сделать десятки почти одинаковых страниц с двумя полями и одной кнопкой. Формально функций много, но продукт начинает выглядеть как коллекция шаблонных SEO-инструментов.

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

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

Что я понял за время разработки

Главный вывод: хранить данные локально не обязательно проще, чем хранить их на сервере.

Сложность просто распределяется иначе.

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

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

Фразы «IndexedDB», «локальное хранилище» и «данные остаются в браузере» понятны разработчику. Пользователю важнее знать:

  • сохранится ли текст после закрытия вкладки;
  • можно ли открыть его на другом устройстве;
  • что произойдёт после очистки браузера;
  • как сделать резервную копию;
  • кто имеет доступ к заметкам.

И третий вывод: приватность сама по себе не отменяет ответственность за сохранность данных.

Нельзя сказать: «Мы ничего не отправляем на сервер, поэтому всё остальное — проблема пользователя». Если приложение предлагает хранить заметки, оно должно делать это максимально надёжно и предупреждать об ограничениях до того, как что-то будет потеряно.

Что получилось сейчас

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

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

Например, пока остаются открытыми вопросы:

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

Посмотреть текущую версию можно на textria.ru.

Особенно интересно мнение тех, кто пользуется локальными приложениями: доверили бы вы браузеру важные заметки или без аккаунта и синхронизации такой сервис воспринимается только как временный черновик?

12
2
1
1