Сертифікат про закінчення курсу — відчутний результат для студента, але генерація тисячі PDF за годину — нетривіальне завдання. Якість рендеру, кастомізація шаблонів, пікове навантаження (кінець семестру) — типові вузькі місця. Помилка у виборі інструменту або архітектури призводить до повільної видачі, падіння сервера або низької якості PDF. Ми пропонуємо готове рішення під ключ: асинхронна генерація через чергу завдань, зберігання в S3, QR-верифікація та email-доставка. Наші інженери мають досвід у розробці LMS понад 6 років і реалізували 35+ проєктів. Оцінимо ваш проєкт за один робочий день і запропонуємо оптимальну архітектуру.
Які проблеми вирішує система сертифікатів?
Повільна синхронна генерація. Якщо студент чекає відповіді після завершення курсу — сервер може впасти під навантаженням 500+ одночасних запитів. Рішення: асинхронна черга (BullMQ/Celery) з 3–5 воркерами.
Якість PDF. Бібліотеки на кшталт fpdf дають примітивний дизайн. Ми використовуємо Puppeteer або Playwright — рендеримо HTML/CSS з шаблону: градієнти, користувацькі шрифти, прозорість, QR-код.
Кастомізація без деплою. Шаблони зберігаються в БД (HTML + Handlebars). Викладач змінює дизайн через адмінку — не потрібно перезбирати додаток.
Чому асинхронна генерація критична для LMS?
При піковому навантаженні, наприклад в кінці семестру, система одночасно отримує сотні запитів на сертифікати. Синхронна генерація блокує воркери, кожен PDF створюється послідовно. Сервер може не витримати. Асинхронна черга (BullMQ/Celery) розподіляє завдання між воркерами, студент отримує сповіщення одразу, а генерація відбувається у фоновому режимі. На практиці 3 воркери Puppeteer обробляють 1200 PDF за 40 хвилин. Це надійно та масштабовано.
Як вибрати інструмент генерації PDF?
| Інструмент | Мова | Якість рендеру | Швидкість | Складність | Краще для |
|---|---|---|---|---|---|
| Puppeteer/Playwright | Node.js | Відмінна (CSS, шрифти, градієнти) | Середня (витрати пам'яті ~200MB) | Середня | Складні дизайни, брендування |
| WeasyPrint | Python | Хороша (HTML/CSS) | Висока | Низька | Прості сертифікати, висока продуктивність |
| PDFKit | Node.js | Програмне малювання | Висока | Висока | Точний контроль позиціонування, динамічні елементи |
Ми частіше вибираємо Puppeteer — він рендерить будь-який веб-дизайн «піксель в піксель». Код генерації — пара десятків рядків.
Як організована асинхронна генерація?
Студент завершує курс. Якщо оцінка прохідна, в чергу (BullMQ/Celery) потрапляє завдання. Воркер забирає його: завантажує дані студента та курсу, рендерить HTML-шаблон з Handlebars, генерує PDF через Puppeteer, завантажує в S3, зберігає запис у БД і відправляє email з посиланням.
Приклад шаблону:
<!-- certificate-template.hbs --> <!DOCTYPE html> <html> <head> <style> @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Open+Sans&display=swap'); body { margin: 0; width: 297mm; height: 210mm; font-family: 'Open Sans'; } .container { position: relative; width: 100%; height: 100%; } .background { position: absolute; width: 100%; height: 100%; } .content { position: relative; z-index: 1; padding: 40mm 30mm; text-align: center; } .student-name { font-family: 'Playfair Display'; font-size: 36pt; color: #1a1a2e; } .course-name { font-size: 18pt; color: #16213e; margin: 8mm 0; } .cert-id { font-size: 8pt; color: #666; position: absolute; bottom: 10mm; left: 15mm; } .qr-code { position: absolute; bottom: 10mm; right: 15mm; width: 25mm; } </style> </head> <body> <div class="container"> <img class="background" src="{{backgroundUrl}}" /> <div class="content"> <p>Настоящим подтверждается, что</p> <div class="student-name">{{studentName}}</div> <p>успешно завершил(а) курс</p> <div class="course-name">{{courseName}}</div> <p>{{completionDate}} · {{totalHours}} часов</p> </div> <div class="cert-id">ID: {{certificateId}}</div> <img class="qr-code" src="{{qrCodeDataUrl}}" /> </div> </body> </html> Модель даних у PostgreSQL:
CREATE TABLE certificates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), certificate_id VARCHAR(50) UNIQUE NOT NULL, -- CERT-2026-001234 student_id UUID REFERENCES users(id), course_id UUID REFERENCES courses(id), template_id UUID REFERENCES certificate_templates(id), pdf_url VARCHAR(2000), issued_at TIMESTAMPTZ DEFAULT NOW(), revoked_at TIMESTAMPTZ, -- NULL якщо активний revoke_reason TEXT, metadata JSONB DEFAULT '{}' -- score, hours, instructor ); Верифікація та масштабування
Публічна сторінка верифікації без авторизації: GET /verify/{certificate_id} — відображає дані сертифіката та підтверджує його справжність. QR-код (Wikipedia: QR-код) використовується для швидкої верифікації.
app.get('/verify/:certId', async (req, res) => { const cert = await db.certificates.findOne({ certificateId: req.params.certId }); if (!cert || cert.revokedAt) { return res.status(404).render('certificate-invalid'); } res.render('certificate-valid', { studentName: cert.student.name, courseName: cert.course.name, issuedAt: cert.issuedAt, }); }); При піковому навантаженні (кінець семестру, 1000+ сертифікатів) запускаємо кілька worker'ів паралельно. Puppeteer вимогливий до пам'яті (~200MB на екземпляр браузера) — використовуємо пул із 3–5 браузерів через puppeteer-cluster. На практиці 3 воркери обробляють 1200 PDF за 40 хвилин. Порівняння синхронного та асинхронного підходу:
| Аспект | Синхронна | Асинхронна |
|---|---|---|
| Час очікування студента | Залежить від навантаження | Одразу отримує сповіщення |
| Навантаження на сервер | Високе при піку | Розподілена чергою |
| Складність реалізації | Низька | Середня (черга, воркери) |
| Масштабованість | Погана | Хороша (додавання воркерів) |
Що входить у нашу роботу
- Аналіз поточної LMS та потоків студентів
- Проєктування архітектури (вибір інструменту, черги, зберігання)
- Розробка генератора PDF та шаблонів (HTML+Handlebars)
- Інтеграція API верифікації з QR-кодом
- Налаштування асинхронної черги та масштабування
- Тестування (навантажувальне 500-2000 запитів, pixel-perfect перевірка)
- Деплой у ваше середовище (Docker, CI/CD)
- Документація та 3 місяці супроводу
- Гарантія продуктивності та конфіденційності даних
Строки та вартість
Базова система з одним шаблоном, генерацією через Puppeteer, зберіганням у S3 та email-відправкою — 4–5 днів. Візуальний редактор шаблонів для адміністратора та публічна верифікація — ще 3–4 дні. Підсумок: від 4 до 9 днів під ключ. Вартість розраховується індивідуально — зв'яжіться з нами для оцінки. Отримайте консультацію інженера: обговоримо архітектуру, строки та вартість вашого проєкту. Замовте розробку системи сертифікатів під ключ — автоматизуйте видачу та економте ресурси.







