Loading...

How Do You Render HTML with Playwright?

Need to render HTML programmatically? Discover real-world experiences and practical solutions for rendering HTML content using Playwright.

Back

Rendering HTML with Playwright: A Developer's Journey

As a developer who's worked extensively with web automation, I've encountered numerous scenarios where I needed to render HTML programmatically. While there are many tools available, I've found Playwright to be particularly powerful and developer-friendly. In this post, I'll share my experiences and practical solutions for rendering HTML using Playwright.

Real-World Use Cases I've Solved

Let me share some interesting problems I've tackled using Playwright for HTML rendering:

  1. Dynamic Email Templates One of our clients needed to generate thousands of personalized email templates with charts and graphs. Instead of using traditional templating engines, we used Playwright to render React components with dynamic data and capture them as images. This gave us pixel-perfect results and maintained consistency across email clients.

  2. Automated Social Media Cards For our blog platform, we needed to generate unique social media cards for each post. Using Playwright, we built a system that takes article metadata, renders it with custom HTML/CSS, and creates beautiful Open Graph images automatically.

  3. PDF Report Generation We had a complex reporting system that needed to generate PDFs with interactive charts. Traditional PDF libraries struggled with modern CSS and JavaScript charts, but Playwright handled it perfectly.

Let me show you how I implemented these solutions.

Getting Started with Playwright

First, let's set up our project. I prefer using TypeScript for better type safety:

npm init playwright@latest
npm install @playwright/test typescript

Dynamic Email Template Generation

Here's a real example from my project that generates email templates:

import { chromium } from '@playwright/test';
import { readFileSync } from 'fs';
 
async function generateEmailTemplate(userData: UserData) {
  const browser = await chromium.launch();
  
  try {
    const page = await browser.newPage();
    
    // I load a base template and inject user data
    const template = readFileSync('./templates/email.html', 'utf8');
    const htmlContent = template.replace(
      '{{userData}}', 
      JSON.stringify(userData)
    );
 
    await page.setContent(htmlContent);
 
    // Wait for charts to render (we use Chart.js in our templates)
    await page.waitForSelector('.chart-container canvas');
    
    // I found that a small delay helps ensure animations are complete
    await page.waitForTimeout(100);
 
    const screenshot = await page.screenshot({
      clip: { x: 0, y: 0, width: 600, height: 400 }
    });
 
    return screenshot;
  } finally {
    await browser.close();
  }
}

Social Media Card Generator

This is my approach to generating social cards:

async function generateSocialCard(post: BlogPost) {
  const browser = await chromium.launch();
  
  try {
    const page = await browser.newPage();
    
    // I use a custom component for social cards
    await page.setContent(`
      <div class="social-card">
        <h1>${post.title}</h1>
        <div class="author">
          <img src="${post.authorAvatar}" />
          <span>${post.authorName}</span>
        </div>
        <div class="gradient-bg"></div>
      </div>
    `);
 
    // Inject my custom styles
    await page.addStyleTag({
      content: `
        .social-card {
          width: 1200px;
          height: 630px;
          padding: 60px;
          background: #1a1a1a;
          color: white;
          font-family: 'Inter', sans-serif;
          position: relative;
        }
        .gradient-bg {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
          height: 200px;
          background: linear-gradient(transparent, #1a1a1a);
        }
      `
    });
 
    return await page.screenshot({
      path: `${post.slug}-social.png`,
      type: 'png'
    });
  } finally {
    await browser.close();
  }
}

Handling Dynamic Content

One challenge I frequently encountered was dealing with dynamic content. Here's my solution:

async function renderDynamicContent() {
  const browser = await chromium.launch();
  
  try {
    const page = await browser.newPage();
    
    // Set a realistic viewport
    await page.setViewportSize({ width: 1280, height: 720 });
 
    await page.setContent(`
      <div id="app">Loading...</div>
      <script src="https://unpkg.com/vue@3"></script>
      <script>
        // Your dynamic content logic
      </script>
    `);
 
    // I've found this combination of waiting strategies to be most reliable
    await Promise.all([
      page.waitForSelector('#app:not(:empty)'),
      page.waitForFunction(() => {
        return !document.querySelector('.loading');
      }),
      page.waitForLoadState('networkidle')
    ]);
 
    return await page.screenshot();
  } finally {
    await browser.close();
  }
}

Pro Tips from My Experience

After working with Playwright for over two years, here are some valuable lessons I've learned:

  1. Browser Resource Management
// I maintain a browser instance pool for better performance
class BrowserPool {
  private static instance: Browser;
  
  static async getInstance() {
    if (!this.instance) {
      this.instance = await chromium.launch({
        args: ['--no-sandbox']
      });
    }
    return this.instance;
  }
}
  1. Error Recovery I've found this error handling approach to be robust:
async function renderWithRetry(html: string, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const browser = await BrowserPool.getInstance();
      const page = await browser.newPage();
      await page.setContent(html);
      return await page.screenshot();
    } catch (error) {
      console.error(`Attempt ${i + 1} failed:`, error);
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Conclusion

After extensive use of Playwright for HTML rendering, I can confidently say it's one of the most reliable tools in my development arsenal. While solutions like html2canvas or screenshot APIs have their place, Playwright's flexibility and powerful API have helped me solve complex rendering challenges that would have been difficult or impossible with other tools.

The key is to understand your specific use case and requirements. For my projects, Playwright's ability to handle modern web features, combined with its excellent performance and reliability, has made it an invaluable tool for HTML rendering tasks.

Feel free to reach out if you have questions about implementing any of these solutions in your projects. I'm always happy to share more detailed insights from my experience!


Related Resources:

Note: If you're looking for a managed solution, screenshotsapi.dev offers professional HTML rendering services with features like dynamic content support, custom styling, and screenshot capture - all through a simple API without managing your own infrastructure.

Want to learn more about Playwright? Check out our other guides:

Written by

Durgaprasad Budhwani

At

Tue Jan 02 2024