Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, IT partner for business & fractional CTO. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Generates: pi-pi.ee — i18n B2B Presentation PDF Generator

March 4, 2026

On-demand PDF presentation generator for 31 European markets — one codebase, one URL pattern, streamed directly from a Next.js route handler with zero storage and ~200 ms generation time.

Live demo

Stack

Next.jsTypeScript

Libraries

react-pdfnext-intl

Services

Vercel

Topics

PDFDocument Automationi18nB2B

Key Results

  • Reach prospects in their own language — a ready sales deck in any of 31 markets, with zero manual work per market
  • Sales share one link — no translating, reformatting or exporting a slide deck per prospect
  • Decks are never out of date — prices and specs stay in sync with the live site automatically
  • No translator cost — content comes straight from the site's existing translations
  • Nothing new to run or pay for — the PDF is generated on demand, no storage or queue
pi-pi.ee — i18n B2B Presentation PDF Generator

The Business Problem

The pi-pi.ee sales team sells waterless urinal systems across 31 European markets. Reaching distributors and facility managers means communicating in their language — and that communication starts with a product presentation.

The existing workflow: a single PowerPoint file, maintained manually, in one language. When the sales team needed to send a deck to a prospect in Poland or Estonia, someone had to translate it, reformat it, and export it to PDF. Each iteration of the product — new prices, updated specs, different material grades — required the same process all over again. With 31 potential markets, the problem was structurally unsolvable with manual tooling.

The core requirements:

  1. 31 locales — same layout and branding across all markets, each in the local language
  2. Shareable link — pi-pi.ee/en/presentation.pdf, pi-pi.ee/et/presentation.pdf, pi-pi.ee/de/presentation.pdf opens directly in the browser; no login, no form, no download page
  3. Always in sync — content lives in the site's own i18n files; update specs or prices on the site, presentation updates automatically
  4. Zero infrastructure overhead — no storage buckets, no generation queues, no separate CMS

The Solution

I built a Next.js App Router route handler at /[locale]/presentation.pdf that generates the PDF on demand and streams it directly in the HTTP response. The key architectural decision: the presentation reuses the site's existing next-intl translation files verbatim — no separate content, no translators, no duplication. The same JSON keys that power the product pages, pricing tables, and feature lists on the website are read by the PDF route and composed into a structured sales deck.

The PDF is assembled with @react-pdf/renderer — React components describe the layout declaratively, and the whole document compiles to a PDF byte stream on the server. No file is saved to disk, and nothing is uploaded to a storage bucket: the PDF exists only for the duration of the request. When the product team edits a spec or price on the site, the next request to that market's presentation URL picks up the change automatically — no rebuild, no deploy, no manual sync.

Slide Structure

The presentation is four A4 pages, each with a distinct purpose:

Slide 1 — Cover. Company logo, product hero image, tagline, and the sales manager's name and contact details. The contact block is locale-aware — different markets may have different account managers.

Slide 2 — Manufacturer Capabilities. A grid of proof points: factory certifications, production capacity, material grades, export geography. All text comes from the site's existing capabilities message namespace.

Slide 3 — Dual Product Specs. Side-by-side comparison of the two main product lines with dimensions, flow rates, compatibility, and installation requirements. Uses a two-column View grid with aligned label/value rows.

Slide 4 — LLDPE Material Deep-Dive. The most layout-intensive slide: a 3×2 grid of stat cards (tensile strength, temperature range, chemical resistance, UV stability, recyclability, service life), followed by a comparison table of pi-pi material versus standard alternatives.

Results

The presentation is live at pi-pi.ee/[locale]/presentation.pdf for all 31 locales. B2B managers now share a single URL in their outreach emails. The recipient gets a professionally formatted presentation in their language — with specs, pricing, and copy that always match the live site — without any manual preparation or translator involvement.

MetricValue
Locales31 (full EU coverage + other languages)
Generation time~200 ms (A4, 4 slides, images + tables)
Storage requiredNone — generated on demand
Translators involvedZero — reuses existing site i18n files
Content syncAutomatic — site update = PDF update
DeploymentVercel serverless, scales automatically

Under the Hood

For the technically curious, here is how the core pieces are built.

Route Handler — Generate and Stream

The route reads the locale from the URL, loads the relevant message namespaces from the site's i18n files, renders the PresentationDocument component tree, and writes the resulting bytes directly to the response:

// app/[locale]/presentation.pdf/route.ts
export async function GET(
  _req: Request,
  { params }: { params: Promise<{ locale: string }> }
) {
  const { locale } = await params;
 
  const messages = await Promise.all([
    getBaseMessages(locale),
    getUseCasesMessages(locale),
  ]);
 
  const pdfBuffer = await renderToBuffer(
    <PresentationDocument
      messages={messages}
      {...extractors(getMessages(locale))}
    />
  );
 
  return new Response(Buffer.from(pdfBuffer), {
    headers: {
      "Content-Type": "application/pdf",
      "Content-Disposition": `inline; filename="pi-pi-presentation-${locale}.pdf"`,
    },
  });
}

No file saved to disk. No upload to a storage bucket. The PDF exists only for the duration of the request.

Reusing Site i18n Files

The route imports message loaders that are shared with the rest of the Next.js app. There is no separate content management step for the presentation:

// lib/pdf/messages.ts — reads the same files next-intl uses on the site
import { getMessages } from "@/i18n/server";
 
export async function getPresentationMessages(locale: string) {
  const messages = await getMessages({ locale });
 
  return {
    cover: messages.presentation.cover,
    capabilities: messages.capabilities,
    products: messages.products,
    material: messages.material,
  };
}

When the product team edits a spec value in messages/de.json to update a Polish-language page on the site, the next request to /de/presentation.pdf picks up the change automatically. No rebuild, no deploy, no manual sync.

Non-Latin Fonts and Hyphenation

@react-pdf/renderer uses its own text rendering engine and does not inherit system fonts or browser fonts. Without explicit font registration, any character outside the Latin-1 range renders as an empty rectangle. The fix is to register a full Unicode font with an explicit CDN URL before the document renders:

// lib/pdf/fonts.ts
Font.register({
  family: "Roboto",
  fonts: [
    { src: "https://fonts.gstatic.com/.../Roboto-Regular.ttf", fontWeight: 400 },
    { src: "https://fonts.gstatic.com/.../Roboto-Bold.ttf", fontWeight: 700 },
    { src: "https://fonts.gstatic.com/.../Roboto-Italic.ttf", fontStyle: "italic" },
  ],
});
 
// Disable automatic hyphenation globally.
// The built-in algorithm is English-only — applied to non-Latin locales
// it produces incorrect line breaks mid-word.
Font.registerHyphenationCallback((word) => [word]);

The hyphenation callback is the less obvious one: @react-pdf/renderer runs a hyphenation pass on every text node by default. For non-English locales this produces broken line breaks. Returning [word] as a single-element array tells the engine to never split the word, which gives correct results for all 31 locales at once.

Stale Module Cache in Development

Because the presentation reads from the same i18n JSON files as the rest of the site, hot-reload should work automatically — edit a message, refresh the PDF URL, see the change. In practice, the PDF route had its own module-level Map that cached parsed message objects between requests. The cache survived Next.js hot-reload, so edits to JSON files had no effect until a full server restart.

The fix is a single environment check:

// lib/pdf/messages.ts
const cache = new Map<string, Messages>();
 
export async function getMessages(locale: string): Promise<Messages> {
  if (process.env.NODE_ENV !== "development" && cache.has(locale)) {
    return cache.get(locale)!;
  }
  const messages = await loadJSON(locale); // same files the site uses
  cache.set(locale, messages);
  return messages;
}

In development, the cache is bypassed entirely — every request re-reads the JSON from disk. In production on Vercel, the module-level cache persists for the lifetime of the serverless function instance, which is the desired behaviour: warm requests are fast without redundant I/O.

Iurii RoguliaAvailable

Need something similar?

I build custom solutions — from APIs to full products. Let's talk about your project.

View all projects

Related projects

pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
January 17, 2026
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems

i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.

Stack

Next.jsReactTypeScriptTailwind CSS

Libraries

next-intlDaisyUIStripe SDKreact-pdf

Services

StripeVercelResendNotionGoogle Analytics

Topics

E-commerceB2Bi18nPDFSSRArchitectureSEOSchema.org
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
October 12, 2024
Pikkuna — E-commerce for Vinyl Curtains & PVC Products

International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe → Zoho CRM → Airtable → Mailgun →

Stack

Next.jsReactTypeScript

Libraries

next-intlZodpdf-libPuppeteerStripe.js

Databases

Redis

Services

StripeZohoMailgunPostNordNetvisorVercelVercel BlobGoogle AnalyticsMeta CAPIAirtableUpstash

Topics

E-commercePaymentsShippingPDFPWACTOArchitectureSEOSchema.orgi18nPerformance

Related posts

Puppeteer vs react-pdf: Node.js PDF Generation Compared (2026)
August 20, 2025· 16 min
Puppeteer vs react-pdf: Node.js PDF Generation Compared (2026)

Puppeteer vs @react-pdf/renderer for Node.js PDF generation, decided by production use, not benchmarks: which survives Vercel's serverless limits, which

Stack

Next.jsTypeScriptNode.js

Libraries

Puppeteerreact-pdf

Services

Vercel

Topics

PDFE-commerce
React Email + Resend Tutorial: Transactional Emails with PDF Invoices
April 5, 2026· 13 min
React Email + Resend Tutorial: Transactional Emails with PDF Invoices

Production transactional emails with React Email and Resend: PDF invoice generation via react-pdf, Vercel Blob temporary storage, and BullMQ Stripe webhook

Stack

Next.jsTypeScriptReact

Libraries

React EmailStripe SDKStripe.jsreact-pdf

Services

Vercel BlobResend

Topics

E-commercePaymentsDocument Automation