cv-pdf-html-render-playbook

star 0

Standaard werkwijze voor stabiele A4 CV templates met web/PDF-pariteit, inclusief paginering, meting en debug-stappen.

Cevace By Cevace schedule Updated 6/11/2026

name: CV PDF/HTML Render Playbook description: Standaard werkwijze voor stabiele A4 CV templates met web/PDF-pariteit, inclusief paginering, meting en debug-stappen.

CV PDF/HTML Render Playbook (Cevace / YorFutur)

Deze skill is de praktische leidraad voor het bouwen en tunen van CV templates zodat:

  • de browser preview en PDF hetzelfde gedrag tonen;
  • A4-marges stabiel blijven;
  • page-breaks voorspelbaar zijn;
  • we sneller kunnen debuggen zonder opnieuw het wiel uit te vinden.

1. Kernprincipe: Altijd 3 lagen synchroon

Elke template met paginering heeft minimaal 3 lagen die op elkaar moeten aansluiten:

  1. Render (HTML preview)
    Voorbeeld: components/cv-templates/*PagedTemplate.tsx
  2. Measure (hoogtemeting off-screen)
    components/cv-templates/CVContentMeasure.tsx
  3. Pagination logic (blokverdeling over pagina’s)
    hooks/useCVPagination.ts

Als je maar 1 laag aanpast, krijg je bijna altijd mismatch tussen browser en PDF of onverwachte breaks.

2. Bestandskaart per template

Voor nieuwe of bestaande templates altijd deze map volgen:

  • Web template: components/cv-templates/<Template>PagedTemplate.tsx
  • PDF template: components/cv-templates/<Template>PagedTemplatePDF.ts
  • Meting: components/cv-templates/CVContentMeasure.tsx
  • Paginering: hooks/useCVPagination.ts
  • Registratie template-id/labels: types/cv-templates.ts + eventuele registry-bestanden

3. A4-regels (praktisch)

  • A4 = 210mm x 297mm
  • Kleine mm-aanpassingen lijken soms “sprongsgewijs” effect te geven door:
    • block-splitting (atomic chunks),
    • orphan-protection,
    • line wrapping op woordgrenzen.

Richtlijn:

  • ~2 regels extra ruimte is vaak circa 8-10mm effectief, maar altijd in combinatie met paginering.

4. Standaard wijzigingsvolgorde

Bij requests als “nog 2 regels onderaan” of “talen toevoegen”:

  1. Template render aanpassen (TSX/PDF)
    Layout, volgorde, labels, secties.
  2. Measure aanpassen
    Exact dezelfde blokken/volgorde meetbaar maken met data-block-id.
  3. Pagination aanpassen
    getTemplateUsableHeightPx + eventuele template-specifieke reserve/orphan-rules.
  4. Verifiëren
    npm exec tsc --noEmit + visuele check browser + PDF.

5. Bekende valkuilen en oplossing

A) Bottom overflow op pagina 1

Symptoom:

  • Tekst loopt onderaan tegen rand of breekt te vroeg.

Oplossing:

  • Niet alleen padding wijzigen.
    Altijd zowel:
    • bottom padding in *PagedTemplate.tsx
    • usable height in useCVPagination.ts

B) Sectie aanwezig in PDF maar niet in web (of andersom)

Symptoom:

  • Bijvoorbeeld Talen ontbreekt in preview of schuift vreemd.

Oplossing:

  • Sectie toevoegen in alle 3 lagen:
    • render,
    • measurement (data-block-id),
    • pagination block creation (createBlocks).

C) Grote sprongen bij mini-aanpassingen

Symptoom:

  • 0.5-1mm wijziging geeft ineens meerdere regels verschil.

Oplossing:

  • Check of blokken atomic gesplitst worden (splitHtmlContent).
  • Check orphan protection voor dat template.
  • Tune in kleine stappen, maar op bloklogica (header+first body fit), niet alleen op mm.

6. Professionele template-aanpak (specifieke notitie)

Als we professional aanpassen:

  • Talen direct boven Opleidingen plaatsen in render én blockvolgorde.
  • Bottom fix op pagina 1 uitvoeren via bewezen dubbel-aanpak:
    • template padding
    • template usable-height in paginator
  • Daarna pas micro-finetune.

7. Definition of Done

Template is “klaar” als:

  1. Browser preview en PDF dezelfde sectievolgorde tonen.
  2. Geen tekst tegen top/bottom rand op pagina 1 of 2+.
  3. Geen extra lege eindpagina.
  4. Belangrijke secties (Profiel, Kernvaardigheden, Talen, Werkervaring, Opleidingen) correct aanwezig volgens afgesproken volgorde.
  5. TypeScript check is groen.

8. Snelle debug-checklist (copy/paste)

  1. Is de sectie toegevoegd in render?
  2. Is de sectie toegevoegd in CVContentMeasure met data-block-id?
  3. Is de sectie opgenomen in createBlocks in useCVPagination?
  4. Kloppen template-specifieke heights in getTemplateUsableHeightPx?
  5. Is orphan protection te streng voor dit template?
  6. Is bottom padding consistent met verwachte usable space?
  7. npm exec tsc --noEmit groen?

9. Server-Side PDF Generatie in Moderne Next.js App Router Routes

Het Probleem: React Error #31 & RSC JSX Runtime Conflict

In moderne Next.js App Router routes kunnen JSX-tags in Server Components, Server Actions en Route Handlers door de React Server Components (RSC) JSX-runtime van Next.js worden gebundeld. Wanneer deze getranspileerde objecten rechtstreeks worden doorgegeven aan @react-pdf/renderer (bijvoorbeeld via renderToBuffer of pdf().toBlob()), faalt de custom reconciler met: Minified React error #31: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). Dit komt doordat de Next.js bundler JSX compileert tot RSC-metadata in plaats van de standaard client-side elementen die @react-pdf/renderer verwacht.

De Oplossing: Geïsoleerde Node-processen

Om de Next.js Webpack/Turbopack bundler en RSC-runtime volledig te omzeilen:

  1. Plaats PDF-render logica in een extern script: Maak een los TypeScript script aan (bijvoorbeeld in scripts/generate-pdf.ts of scripts/generate-cv-pdf.ts).
  2. Importeer en compileer direct in Node: Laat het script data inlezen via een JSON-bestand of stdin, roep React.createElement en renderToBuffer of renderToFile aan, en schrijf het resultaat weg. Vermijd JSX in het PDF-document zelf als het document ook door Next kan worden gebundeld; gebruik dan React.createElement in de PDF-component.
  3. Voer uit met npx tsx: Gebruik child_process.exec (of execAsync) vanuit de Route Handler of Server Action om het script geïsoleerd te draaien:
    await execAsync(`npx tsx scripts/generate-pdf.ts "${inputJsonPath}" "${outputPdfPath}"`);
    
  4. Schakel logs uit: Demp console.log in het externe script om te voorkomen dat stderr of stdout vervuild raakt en buffers beschadigd raken.
  5. Valideer het resultaat: Controleer in de route dat de output begint met %PDF, zet Content-Type: application/pdf, Content-Disposition, Cache-Control: private, no-store, max-age=0 en ruim scratch-bestanden altijd op in finally.

Cevace-voorbeelden

  • CV PDF: app/api/generate-cv-pdf/route.ts + scripts/generate-cv-pdf.ts
  • Pro rapport: app/api/pro-reports/[reportId]/route.ts + scripts/generate-pdf.ts
  • Assessment rapport: app/api/assessment/report/route.ts + scripts/generate-assessment-report-pdf.ts

Assessmentrapport-specifieke notitie

  • Template: components/pdf/AssessmentReportPDF.tsx
  • Logo: public/logo/Cevace-zwart-stapel.jpg, in het externe script ingelezen als data-URL zodat de serverroute niet afhankelijk is van externe assets.
  • Brandingregel: Cevace-logo zichtbaar in de header; titel en assessmentmetadata ruim scheiden met margin/padding en een subtiele lijn.
  • Regressietest: tests/e2e/assessment-v2.spec.ts bevat een PDF-downloadtest die content-type, bestandsnaam, %PDF en bestandsgrootte controleert.

Voorbeeld Script (scripts/generate-pdf.ts):

import React from 'react';
import { renderToBuffer } from '@react-pdf/renderer';
import MyDocumentPDF from '../components/pdf/MyDocumentPDF';
import fs from 'fs';

async function main() {
  const [inputPath, outputPath] = process.argv.slice(2);
  try {
    const data = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
    
    // Voorkom stdout vervuiling
    console.log = () => {};
    console.warn = () => {};
    console.info = () => {};
    
    const element = React.createElement(MyDocumentPDF, { data });
    const buffer = await renderToBuffer(element);
    
    fs.writeFileSync(outputPath, buffer);
    process.exit(0);
  } catch (err: any) {
    process.stderr.write(`Error: ${err.message}\n`);
    process.exit(1);
  }
}
main();
Install via CLI
npx skills add https://github.com/Cevace/YorFutur --skill cv-pdf-html-render-playbook
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator