TL;DR
LocalPDF — это 100% клиентское приложение для работы с PDF, построенное на Next.js и WebAssembly. Оно позволяет выполнять 21 операцию с PDF (мерж, сплит, компрессия и др.) без загрузки файлов на сервер, работая полностью оффлайн. Разбираем архитектуру и ключевые техники реализации.
Введение: контекст и боль
Проблема облачных PDF-редакторов очевидна: загружая sensitive-документы на сторонние сервера, мы теряем контроль над данными. В эпоху GDPR и CCPA это неприемлемо. Решение — перенести всю логику обработки на клиент, используя современные браузерные технологии.
Архитектура решения
Core Stack
// next.config.js
const withWASM = require('@next/webassembly')
module.exports = withWASM({
webpack(config) {
config.experiments = { asyncWebAssembly: true }
return config
}
})
Ключевые компоненты:
- Next.js как framework для routing и code-splitting
- WebAssembly для выполнения CPU-intensive операций
- PDFium или pdf-lib в WASM-варианте
Как работает WASM-интеграция
// Пример экспорта функции из C++ в WASM
EMSCRIPTEN_KEEPALIVE
void merge_pdfs(const char** file_buffers, int* file_sizes, int count) {
// Логика мержа PDF
}
Компиляция через Emscripten:
emcc -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_merge_pdfs']" -o merge.js merge.cpp
Продвинутые техники
Оффлайн-работа с Service Worker
// service-worker.js
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/wasm/')) {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request)
})
)
}
})
Работа с большими файлами
Используем File System Access API для оптимизации памяти:
const fileHandle = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const stream = await file.stream();
const pdfBytes = await new Response(stream).arrayBuffer();
Бенчмарки и оптимизации
-
WASM Memory Management:
- Пул буферов для избежания частых аллокаций
- Стратегическое использование
malloc()/free()
-
UI-блокировки:
- Web Workers для тяжелых операций
- Progressive rendering через
requestIdleCallback
// worker.ts
self.onmessage = async (e) => {
const { operation, payload } = e.data;
const result = await wasmModule[operation](payload);
self.postMessage({ result });
};
Практическое применение
Реальные кейсы
- Юридические документы: Redact sensitive данных без риска утечки
- Медицина: Обработка сканов без HIPPA-рисков
- Образование: Разделение учебных материалов на главы
Лимитации
- Размер файлов ограничен RAM устройства
- Производительность на low-end устройствах
- Browser compatibility (особенно mobile)
Заключение
LocalPDF демонстрирует мощь современного frontend-стэка. Комбинация Next.js для UI и WASM для тяжелых вычислений открывает новые возможности для privacy-first приложений. Главный урок: многие “серверные” задачи теперь можно выполнять на клиенте без компромиссов в безопасности.
Для дальнейшего развития проекта стоит рассмотреть:
- WASM SIMD оптимизации
- IndexedDB для persistent storage
- WebGPU-ускорение для рендеринга
P.S. Для тех, кто хочет копнуть глубже — исходники на GitHub (ссылка в комментариях).
Источник: https://www.reddit.com/r/webdev/comments/1sigxtp/i_built_a_100_clientside_pdf_studio_with_21/