Эра Local-first
приложений
рандомный день 18.10.2026
Как мы вообще строим приложения?
- документы
- Google Docs
- почта
- Gmail
- доска
- Miro
- задачи
- Jira
Клиент
Сервер
Интерфейс
Данные
запрос
─────────────────────────────────►
ответ
◄─────────────────────────────────
И... Как оно там?
11.10.2026 | Коммерсантъ
Дата-центр «Яндекса» во Владимире был атакован БПЛА
14.10.2026 | Интерфакс
Сбербанк подтвердил факт атаки на ЦОД в Саратовской области
Интернет в целом умирает
Россия
Мобильный интернет ограничивают при угрозе БПЛА. Ведь только при угрозе, да?
Иран
Трафик к глобальному интернету падает почти до нуля.
Великобритания
Проверка возраста для доступа к порнографии.
Австралия
Аккаунты в соцсетях только с 16 лет.
Вьетнам
Публикации и комментарии только с подтверждённого по паспорту аккаунта.
А если сервис передумает?
Notion
───────────
Документы и проекты
Страна или история платежей
Доступ из РФ закрыт. Пространства с платёжной историей РФ закрывают.
───────────
Почта и документы
Два года без активности
Личный аккаунт и его данные могут удалить.
───────────
Сохранённые статьи
Компания закрыла продукт
Сервис закрылся 08.07.2025.
Discord
───────────
Переписка и сообщества
Оспоренное списание через банк
Аккаунт могут приостановить на время проверки.
Так что такое Local-first и как он нам поможет?
Где живут данные?
Cloud-firstLocal-first
.,:;;---;--;;;;:,,,
::;;;-=-==--=-===----;::,
:;;-==-===+++=+=++==-=-;-;;::
,,::;;;;;;;;;;;--=++++=++=++==++====-;-;;;;;;;;;;:::
,:;;;-==-==-====--===+=+++++*++==++==-=----=-=-=-=---;;;,,
:;;--=-=+======+===-===++++++++++++=====--========+===-==-;:::
,:---==+=+===+++=+++==+==+=++=+==++=+=+==--=++=+=+=+++=+===--;:::
,;;;=========+++=++=++=+==+=+=++=+=+=+=-=====+++++=++++==+----;-;;,
,:;===-=++++++++++=+=++=+=-====-======--=-======++++++=++===-==-;;:,,
:;;--==++=++++++*+++=+=+=---=-=---===----==+===++++++=++++====---;::,
:;;-=-===++++++++=+++=+=+=--===+=======----==+=++===+=+++=-=----;-;::
::;;=-=-=============+=+=+++=++=++==+===++=+==+++++=+====-=-=--;;;::,
,:;--=--==+==+==+======++=++++=*+=+++=++=++=============--=----;;:::.
::;;;;-========++==++=++=++++++==++==++++=======---==-------;-;:::,
,:;;;;---=-=--==+==++++++=+=+=+=+==+++=+=====-=----;;-;---;;;;:,,
:::;;;;--====-=======+==++=++=+===-===---==-------;;-:;;::::,,
,,,;:-;----=-===-===-==---===-==---=-=-----;;;-;;;;::::,,,,
.:::::;-------;-==-----=;-;--;---;;-;;;-:;::;::::::,.
.,,:;:;:;;;-;;;;-;--;;;;;;;;;;;;;;::;:::::,:...
..,,,:::::;;;::::::::::,:::,:,,,,..
>>
//
//
//
//
//
//
<<
<<
\\
\\
\\
\\
\\
\\
>>
.__________________________________________________.
| o |:
| +--------------------------------------------+ |:
| | o o o | project -- x |:
| |--------------------------------------------| |:
| | | | |:
| | + notes | # My project | |:
| | | | |:
| | + tasks | [x] sketch the idea | |:
| | | | |:
| | + files | [ ] write the draft | |:
| | | | |:
| | ` draft | [ ] share with team | |:
| | | | |:
| | + media | __________________________ | |:
| | | | |:
| | | | |:
| | | | |:
| +--------------------------------------------+ |:
|__________________________________________________|:
/====================================================\
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [________] .--------------. \:
/__________________________|______________|__________________\
`-----------------------.__________________.------------------'
.__________________________________________________.
| o |:
| +--------------------------------------------+ |:
| | o o o | project -- x |:
| |--------------------------------------------| |:
| | | | |:
| | + notes | # My project | |:
| | | | |:
| | + tasks | [x] sketch the idea | |:
| | | | |:
| | + files | [ ] write the draft | |:
| | | | |:
| | ` draft | [ ] share with team | |:
| | | | |:
| | + media | __________________________ | |:
| | | | |:
| | | | |:
| | | | |:
| +--------------------------------------------+ |:
|__________________________________________________|:
/====================================================\
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [________] .--------------. \:
/__________________________|______________|__________________\
`-----------------------.__________________.------------------'
.______________.
.-' '-.
( ....)
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
`-.______________________.-'
.______________.
.-' '-.
( ....)
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
`-.______________________.-'
.______________.
.-' '-.
( ....)
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
`-.______________________.-'
<<
\\
\\
\\
\\
\\
\\
>>
>>
//
//
//
//
//
//
<<
.,:;;---;--;;;;:,,,
::;;;-=-==--=-===----;::,
:;;-==-===+++=+=++==-=-;-;;::
,,::;;;;;;;;;;;--=++++=++=++==++====-;-;;;;;;;;;;:::
,:;;;-==-==-====--===+=+++++*++==++==-=----=-=-=-=---;;;,,
:;;--=-=+======+===-===++++++++++++=====--========+===-==-;:::
,:---==+=+===+++=+++==+==+=++=+==++=+=+==--=++=+=+=+++=+===--;:::
,;;;=========+++=++=++=+==+=+=++=+=+=+=-=====+++++=++++==+----;-;;,
,:;===-=++++++++++=+=++=+=-====-======--=-======++++++=++===-==-;;:,,
:;;--==++=++++++*+++=+=+=---=-=---===----==+===++++++=++++====---;::,
:;;-=-===++++++++=+++=+=+=--===+=======----==+=++===+=+++=-=----;-;::
::;;=-=-=============+=+=+++=++=++==+===++=+==+++++=+====-=-=--;;;::,
,:;--=--==+==+==+======++=++++=*+=+++=++=++=============--=----;;:::.
::;;;;-========++==++=++=++++++==++==++++=======---==-------;-;:::,
,:;;;;---=-=--==+==++++++=+=+=+=+==+++=+=====-=----;;-;---;;;;:,,
:::;;;;--====-=======+==++=++=+===-===---==-------;;-:;;::::,,
,,,;:-;----=-===-===-==---===-==---=-=-----;;;-;;;;::::,,,,
.:::::;-------;-==-----=;-;--;---;;-;;;-:;::;::::::,.
.,,:;:;:;;;-;;;;-;--;;;;;;;;;;;;;;::;:::::,:...
..,,,:::::;;;::::::::::,:::,:,,,,..
.______________.
.-' '-.
( ....)
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
| .::|
|`-.____________________.-'|
| .::|
`-.______________________.-'
Ещё один сервер синхронизации
<---------------------------------------------------------------------------------------------------------->
P2P
Основное хранилище Синхронизация и бэкап
Тонкий клиент Толстый клиент
Тонкий клиент Толстый клиент
RPC / REST Синхронизация
RPC / REST Синхронизация
Идеалы Local-first
Для пользователя
- 1.
Мгновенный UI
- 2.
Работа синхронизируется между устройствами
- 3.
Сеть опциональна
- 4.
Совместная работа опциональна
- 5.
Данные переживут компанию
- 6.
Безопасность и приватность
- 7.
Владение и контроль
Для разработчика
- 1.
Реализовать sync легче rest
- 2.
Не нужно поддерживать backend
Для общества
- 1.
Устойчивость к цензуре и блокировкам
И всё это благодаря децентрализации
.________________________.
| o SYNC-01 -- |:
|========================|:
| [:::::] [:::::] o o |:
|------------------------|:
| [:::::] [:::::] o x |:
|------------------------|:
| [:::::] [:::::] x x |:
|------------------------|:
| [ OFFLINE ] |:
|________________________|:
'------------------------':
|__| |__|
.......||................||.......
Сервер
Сбой или авария
--->
Локальная копия Несколько sync-серверов
.__________.
.___| -- -- |___.
| _________________|:
| | [] [] [] [] [] | |:
| | [] [] [] [] [] | |:
| | [] [] [] [] [] | |:
| | [] [] [] [] [] | |:
| | [] [] [] [] [] | |:
| | [] [] [] [] [] | |:
| |______.._______| |:
| | | || | | |:
|_|__|___||___|___|_|:
__/____________________\__
...../__________________________\..
Компания
Закрытие или запрет доступа
--->
Владение данными P2P и свои sync-серверы
.----------.
.--' .--|--. '--.
.' / .| \ '.
/ .:::|::: | | \
/ :::::|:: | | \
|--------|----+----|--------|
| ::: | | .|:::. |
| :: | | ::|:::: |
|--------|----+----|--------|
\ | | :|:: /
\ | | | /
'. \ | / .'
'--. '--|--' .--'
'----------'
Страна
Блокировки
--->
Sync-серверы в разных странах
Окей. А как строить эти Local-first приложения?
Проблема конкурентных изменений
Одна заметка, две копииДва изменения без связиСвязь восстановлена
.__________________________________________________.
| o |:
| +--------------------------------------------+ |:
| | o o o | notes.md -- x |:
| |--------------------------------------------| |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| +--------------------------------------------+ |:
|__________________________________________________|:
/====================================================\
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [________] .--------------. \:
/__________________________|______________|__________________\
`-----------------------.__________________.------------------'
.__________________________________________________.
| o |:
| +--------------------------------------------+ |:
| | o o o | notes.md -- x |:
| |--------------------------------------------| |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| | | |:
| +--------------------------------------------+ |:
|__________________________________________________|:
/====================================================\
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [__][__][__][__][__][__][__][__][__][__][__][__] \:
/ [________] .--------------. \:
/__________________________|______________|__________________\
`-----------------------.__________________.------------------'
Встреча
Начало: 18:00
Начало: 19:00
Встреча
Начало: 18:00
Начало: 20:00
<---------------------------------------------->
----------------- x ------------------
syncнет связи
| | | | | | | | | +-------------------------------------------------------
|
|
|
|
|
|
|
|
|
+------------------------------------------------------+
v
Как объединить изменения и ничего не потерять?
Conflict-free Replicated Data Types
CRDTs
Счётчик
G-Counter
0
+----------+----------+
+1 +1
+----------+----------+
2
Оба инкремента учитываются.
0 → +1 / +1 → 2
Множество
OR-Set
{ }
+----------+----------+
+ чай + кофе
+----------+----------+
{чай, кофе}
Независимые добавления сохраняются.
{ } → + чай / + кофе → {чай, кофе}
Текст
RGA
AB
+----------+----------+
AxB ABy
+----------+----------+
AxBy
Обе вставки остаются в тексте.
artredkiy.com/blog/collaborative-editing
AB → AxB / ABy → AxBy
Automerge
github.com/automerge/automergeБиблиотека для синхронизации данных на основе CRDT.
- Локальные документы, похожие на JSON
- Автоматическое объединение изменений
- Полная история правок
:d88dq
.p8888888,
p888Yqpp888d.
:d88Y, p888q
.p88bq :q .Y888,
p888Y. .p88d, :888d.
:d88Y, p888888d. p88bq
.p888q :d888888888q .Y888,
p888Y. .p88888b888888d, :888d.
:d88Y, p8b88888888888888d. p888q
.p888q :d88888888q:888888888q .Yb88,
p8b8Y. .p888888888q .Y8888888bd, :888d.
:d88Y, p8888888888, pb88888888d. p888q
.p888q :d888888888Y. p8888888888q .Y888,
p888Y. .p88888888b8Y :8888888888d, :888d.
:d88Y, p8888b888888q .Y8888888b88d. p888q
.p888q :d88888888888q .Yb8888888888q .Y888,
p888Y. .p888888888888, p88888888888d, :888d.
:d88Y, p8888888888888. p888888888888d. p888q
.p888q :d88888888888888, :ppq dq,. p888888888b8888q .Y888,
p888Y. .p88888888888b8888888888q .8888888888b88888888888d, :888d.
:d88Y, p8888888b8888888888888888q .8888b8888888888888888888d. p888q
p888q :d88b8888888888888888888888, .88888888888888888888888888q .Y888,
p888q .p88888888888888888888888888Y .888888888888888888888b88888d, 8888
pb88q .p888888888888888888888b888Y. .888888888888888b88888888888Y, 8888
p888q :Y8888888888888b88888888Y. .888888888b8888888888888888q .d888,
:Y88d, p888888b888888888888Y, .888b88888888888888888888Y. p88bq
p888d. .p8888888888888888q .8888888888888888888888Y, :888Y.
.p888q :Y888888888888Y. . .88888888888888888888bq .d888,
:Y88d, p8888888888, :dq .88888888888888b8888Y. p888q
p888d. .p8888b888q. .p88q .88888888b88888888Y, :888Y.
.p888q :Y88888888q p8888q .88b8888888888888q .d888,
:Y88d, p88888888888888q .88888888888888Y. p888q
p888d. .p8888888888b8q .888888888888Y, :8b8Y.
.p888q :Y88b8888888q .88888888888q .d888,
:Y88d, p888888888q,::::::8888888b8Y. p888q
p888d. .p8888888888888888b88888Y, :888Y.
.p888q :Y88888888b8888888888q .d888,
:Y88d, p8b88888888888888Y. p888q
p88bd. .p888888888888Y, :888Y.
.p888q :Y888888888q .d888,
:Y88d, p888888Y. p888q
p888d. .p88Y, :888Y.
.pb88q :q .d888,
:Y88d, p888q
p888dqpp888Y.
.p888888b,
:Y88Yq
Слияние документов
Жёлтым выделены изменения
Локальная копия a
{
"title": "Local-first",
"date": "18.10.2026",
"hour": 19,
"room": "B",
"online": false,
"participants": 12
}
Локальная копия b
{
"title": "Local-first",
"date": "18.10.2026",
"hour": 20,
"room": "A",
"online": true,
"participants": 12
}
| |
+---------------------------------+---------------------------------+
v
Automerge.merge(a, b)
{
"title": "Local-first",
"date": "18.10.2026",
"hour": 20,
"room": "B",
"online": true,
"participants": 12
}
Синхронизация как общая инфраструктура
Заметки
Задачи
Доска
Общий sync-сервер
Серверу не нужна логика конкретного приложения
Для него это документы и изменения.
Мы хотим общедоступные sync-сервера!!
А шифрование же... будет?
Иннокентий
Sync-сервер
Аркадий
Встреча Сегодня в 20:00
Встреча Сегодня в 20:00
a91f 7c03 b82e 03d8 e70a 91bc
─────────────────────────►
─────────────────────────►
шифруем
расшифровываем
#a91f
#03d8
relay
без ключа чтения
Ключи на устройстве
Ключи на устройстве
Ну не всё же так идеально?
- [?]
Слить данные ≠ сохранить смысл
Сходимость ещё не гарантирует правильный результат.
- [?]
Совместимость версий
Старые и новые клиенты должны понимать данные друг друга.
- [?]
Доступ и восстановление ключей
Как вернуть доступ после потери устройства?
END
Презентация доступна по ссылкеchernigin.com/present/local-first