Add PDF Export to Reactive Apps

This guide shows how to add a PDF download button to a reactive app with WebSave as PDF. It covers React, Preact, Vue, Angular, Svelte, Astro, and SolidJS, with a shared configuration and a small component for each framework.

The examples use content mode: WebSave captures the page's rendered HTML and current form values in the browser, then sends the content to PDFCrowd to create the PDF. This suits pages that change as the user interacts with the app.

Load and configure WebSave

Add the configuration and WebSave script once in your application's main HTML file or shared layout:

<script>
  window.webSaveConfig = {
    conversionMode: 'content',
    fileName: 'report.pdf',
    apiSettings: {
      page_size: 'A4'
    }
  };
</script>
<script src="https://edge.pdfcrowd.com/websave/1.3.0/websave.min.js" async></script>

The data-config="webSaveConfig" attribute tells WebSave to use the settings above for that button. Change fileName for the download name and apiSettings for PDF options such as page size or margins. See the WebSave reference for available settings.

Add the button in your framework

WebSave automatically sets up buttons already on the page when its script loads. If your framework adds a button later, call window.WebSave.initButton(button) after adding it to the page.

The examples use ?. to call initButton() only if WebSave has loaded. Otherwise, WebSave will find and set up the button when its script finishes loading.

Each example is a button component you can place beside your page content. The buttons use your application's styles. The pdfcrowd-remove class keeps the button out of the PDF while keeping it visible on the page.

The buttons below use the working demo WebSave key, so you can try them without creating an account. For production, replace it with your WebSave key.

React

Use a ref to get the button element, then initialize it in useEffect after React adds it to the page:

import { useEffect, useRef } from 'react';

export default function PdfDownload() {
  const button = useRef(null);

  useEffect(() => {
    window.WebSave?.initButton(button.current);
  }, []);

  return (
    <button
      ref={button}
      type="button"
      className="pdfcrowd-websave pdfcrowd-remove"
      data-key="demo"
      data-config="webSaveConfig">
      Download PDF
    </button>
  );
}

In Next.js App Router, make this a Client Component by adding 'use client'; before the imports. Load the shared configuration and WebSave script from your layout. The code that sets window.webSaveConfig must run in the browser, since window is not available on the server.

Preact

Use the React component above, replacing its import with Preact's hooks:

import { useEffect, useRef } from 'preact/hooks';

Vue

Use a template ref to get the button element, then initialize it in onMounted after Vue adds it to the page:

<script setup>
import { onMounted, ref } from 'vue';

const button = ref(null);

onMounted(() => {
  window.WebSave?.initButton(button.value);
});
</script>

<template>
  <button
    ref="button"
    type="button"
    class="pdfcrowd-websave pdfcrowd-remove"
    data-key="demo"
    data-config="webSaveConfig">
    Download PDF
  </button>
</template>

The same component works in Nuxt. Run the shared configuration code in the browser, where window is available.

Angular

Use afterNextRender to initialize the button after Angular renders it in the browser:

import { afterNextRender, Component, ElementRef, viewChild } from '@angular/core';

@Component({
  selector: 'app-pdf-download',
  standalone: true,
  template: `
    <button
      #pdfButton
      type="button"
      class="pdfcrowd-websave pdfcrowd-remove"
      data-key="demo"
      data-config="webSaveConfig">
      Download PDF
    </button>
  `
})
export class PdfDownloadComponent {
  private button = viewChild.required<ElementRef<HTMLButtonElement>>('pdfButton');

  constructor() {
    afterNextRender(() => {
      (window as any).WebSave?.initButton(this.button().nativeElement);
    });
  }
}

The main HTML file is usually src/index.html. Import PdfDownloadComponent into the component that uses it, then add <app-pdf-download /> to that component's template.

Svelte

Use bind:this to get the button element, then initialize it in onMount after Svelte adds it to the page:

<script>
  import { onMount } from 'svelte';

  let button;

  onMount(() => {
    window.WebSave?.initButton(button);
  });
</script>

<button
  bind:this={button}
  type="button"
  class="pdfcrowd-websave pdfcrowd-remove"
  data-key="demo"
  data-config="webSaveConfig">
  Download PDF
</button>

For SvelteKit, put the shared configuration and external script in src/app.html. The component's onMount callback runs only in the browser.

Astro

Add the shared configuration and WebSave script to your layout, using is:inline on both setup <script> tags so Astro includes them unchanged in the page's HTML. Then add this button component:

<button
  type="button"
  class="pdfcrowd-websave pdfcrowd-remove"
  data-key="demo"
  data-config="webSaveConfig">
  Download PDF
</button>

<script>
  function initializePdfButtons() {
    document.querySelectorAll('.pdfcrowd-websave').forEach((button) => {
      (window as any).WebSave?.initButton(button);
    });
  }

  initializePdfButtons();
  document.addEventListener('astro:page-load', initializePdfButtons);
</script>

The astro:page-load event also initializes new buttons after navigation with Astro's ClientRouter. If the button is part of a React, Vue, or other framework component that becomes interactive in the browser, use that framework's example instead.

SolidJS

Use a ref to get the button element, then initialize it in onMount after SolidJS adds it to the page:

import { onMount } from 'solid-js';

export default function PdfDownload() {
  let button;

  onMount(() => {
    window.WebSave?.initButton(button);
  });

  return (
    <button
      ref={button}
      type="button"
      class="pdfcrowd-websave pdfcrowd-remove"
      data-key="demo"
      data-config="webSaveConfig">
      Download PDF
    </button>
  );
}

When page content or settings change

Content mode captures the page when the visitor clicks Download PDF. You do not need to initialize the button again when page content or form values change. If navigation or a page update creates a new button, initialize that new button. The examples above do this each time the framework adds the button to the page.

Calling initButton() again on the same element is safe: WebSave does not attach a second click handler. This also works when React runs an effect again in development.

WebSave reads window.webSaveConfig for each click. You can update its filename or PDF settings when your application data changes, without loading the script again:

window.webSaveConfig.fileName = 'quarterly-report.pdf';
window.webSaveConfig.apiSettings.page_size = 'Letter';

If a newly displayed button does nothing, check that the WebSave script loaded and that initialization ran for that button element. For conversion or layout issues, see WebSave troubleshooting.