Сертифікат про закінчення курсу — відчутний результат для студента, але генерація тисячі 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 днів під ключ. Вартість розраховується індивідуально — зв'яжіться з нами для оцінки. Отримайте консультацію інженера: обговоримо архітектуру, строки та вартість вашого проєкту. Замовте розробку системи сертифікатів під ключ — автоматизуйте видачу та економте ресурси.







