Skip to content

Slide Rendering

Deckyard can render slides server-side for exports and previews.

Server-side rendering enables high-quality exports and previews without requiring a browser on the client. Deckyard uses Puppeteer (headless Chromium) to render slides as images.

  1. HTML Generation - Slide content + CSS + fonts assembled into HTML
  2. Browser Rendering - Puppeteer opens the HTML
  3. Screenshot - Viewport captured as PNG buffer
  4. Return - Buffer returned for saving or streaming
// Internal rendering flow
const buffer = await renderSlideToPngBuffer(repoRoot, slide, {
scale: 2, // 2x for retina quality
theme: themeObject
});
SettingValue
Native viewport1600 x 900
Aspect ratio16:9
Scale options1x, 2x, 3x
Output (2x)3200 x 1800
ScaleOutput SizeUse Case
1x1600 x 900Preview, thumbnails
2x3200 x 1800Standard export
3x4800 x 2700High-res print

Generate social media preview images for shared presentations.

PropertyValue
Canvas1200 x 630
Slide areaMax 1120 x 630
Background#121212 (dark gray)
  1. Render first meaningful slide at 1600x900
  2. Resize to fit within OG dimensions
  3. Center on dark canvas
  4. Optionally add author overlay
  5. Return 1200x630 PNG

Optional branding in top-right corner:

await generateOgPreview(repoRoot, slide, theme, {
showAuthor: true,
authorInfo: {
name: 'John Doe',
imageUrl: 'https://...'
}
});

For PDF exports, slides are rendered and assembled into a document:

  1. Each slide rendered as PNG
  2. PNGs combined into PDF
  3. Optional speaker notes added
  4. Multiple layouts supported (1, 2, 4, 6 per page)
  • Gradients rendered as static (deterministic output)
  • Animations frozen at default state
  • High resolution (2x scale)
  • Embedded fonts

Each rendered page includes:

<!doctype html>
<html lang="...">
<head>
<meta charset="utf-8" />
<style>
/* Embedded fonts as base64 */
/* App CSS */
/* Theme variables */
/* Slide styles */
</style>
</head>
<body>
<div class="ps-theme">
<!-- Slide HTML -->
</div>
<script>/* Prism/KaTeX init */</script>
</body>
</html>

Stylesheets are:

  • Inlined (no external requests)
  • Font-faces embedded as data URLs
  • Theme variables applied
  • Imports resolved recursively

Images are converted to data URLs:

// Local images become base64
/uploads/image.jpg → data:image/jpeg;base64,...

This ensures all resources are available during render.

The rendering system is used by:

  • Export endpoints - PDF, PNG export
  • Publish - OG image generation
  • Thumbnails - Slide library previews
POST /api/presentations/:id/slides/:slideId/render
Content-Type: application/json
{
"scale": 2,
"format": "png"
}
Response: PNG binary
Terminal window
# Path to Chromium binary
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# Disable sandbox (Docker)
PUPPETEER_NO_SANDBOX=true

The Dockerfile includes Chromium:

RUN apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ca-certificates \
ttf-freefont

A single browser instance is shared:

let browser = null;
async function getPuppeteerBrowser() {
if (!browser) {
browser = await puppeteer.launch({
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
}
return browser;
}

Fonts are embedded for consistent rendering:

  1. Font files read from disk
  2. Converted to base64
  3. Injected as @font-face with data URLs
  4. Original @font-face rules stripped

If a theme specifies custom fonts:

  • Google Fonts are downloaded
  • Local fonts are embedded
  • Fallback fonts applied if unavailable

Video slides render a static preview:

  • First frame or poster image
  • Play button overlay
  • Caption/title text

Polls, Q&A, and other interactive elements:

  • Render in default/empty state
  • No live data included
  • Placeholder text where appropriate

These are skipped for OG images (first meaningful slide used instead).

  • Browser instance reused
  • Page created per render (cleaned up after)
  • No result caching (renders are cheap)

Each render temporarily uses:

  • ~100-200MB for page
  • Buffer size varies by scale
  • Cleaned up after each render

Default timeout: 30 seconds per render

Long renders may indicate:

  • Large/complex slides
  • Missing resources
  • Network issues
  • Check Chromium is installed
  • Verify PUPPETEER_EXECUTABLE_PATH
  • Check for JavaScript errors in console
  • Ensure fonts are accessible
  • Check theme configuration
  • Verify font embedding worked
  • Reduce scale factor
  • Process fewer slides at once
  • Increase container memory limits
  • Check image sizes
  • Reduce complex animations
  • Verify network for external resources