<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Graham Sutton</title>
    <description>The latest articles on Forem by Graham Sutton (@grahamsutton).</description>
    <link>https://forem.com/grahamsutton</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F174773%2Ff1d2d024-9554-411a-b9c6-49da92cb2d51.jpg</url>
      <title>Forem: Graham Sutton</title>
      <link>https://forem.com/grahamsutton</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/grahamsutton"/>
    <language>en</language>
    <item>
      <title>I Spent a Week Fighting HTML-to-PDF. Here’s What Finally Worked.</title>
      <dc:creator>Graham Sutton</dc:creator>
      <pubDate>Wed, 06 May 2026 21:25:51 +0000</pubDate>
      <link>https://forem.com/grahamsutton/i-spent-a-week-fighting-html-to-pdf-heres-what-finally-worked-4nfp</link>
      <guid>https://forem.com/grahamsutton/i-spent-a-week-fighting-html-to-pdf-heres-what-finally-worked-4nfp</guid>
      <description>&lt;p&gt;I’ve been building a property inspection platform with a friend of mine who’s an inspector. One of the requirements was deceptively simple:&lt;/p&gt;

&lt;p&gt;Generate PDF reports.&lt;/p&gt;

&lt;p&gt;That’s the deliverable inspectors send to insurance companies, banks, clients, etc. So the PDFs need to actually look good. Lots of photos, cover pages, tables of contents, structured sections, all that fun stuff.&lt;/p&gt;

&lt;p&gt;My friend uses &lt;a href="https://spectora.com" rel="noopener noreferrer"&gt;Spectora&lt;/a&gt; right now and honestly their PDFs were pretty good. So I figured, alright, let me build something similar.&lt;/p&gt;

&lt;p&gt;I thought this would take maybe a day or two. It ended up taking me about a week and the PDF output is still brittle.&lt;/p&gt;

&lt;p&gt;First, I started with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wkhtmltopdf&lt;/li&gt;
&lt;li&gt;Puppeteer&lt;/li&gt;
&lt;li&gt;PDF libraries (DomPDF, mPDF, FPDF, etc)&lt;/li&gt;
&lt;li&gt;CSS &lt;a class="mentioned-user" href="https://dev.to/print"&gt;@print&lt;/a&gt; media query layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing really ever worked right. Text would sometimes randomly overflow pages. I would constantly make an update to CSS and then generate a PDF and back and forth and back and forth. Headers and footers were also pain. One thing that drove me insane was that I wanted headers and footers on most pages, but not on the cover page or table of contents.&lt;/p&gt;

&lt;p&gt;Sounds reasonable, right?&lt;/p&gt;

&lt;p&gt;Turns out with a lot of these tools, it’s basically: headers everywhere or headers nowhere at all.&lt;/p&gt;

&lt;p&gt;At one point I had CSS media queries inside of templates inside of rendering configs and I genuinely stopped understanding what controlled what anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that changed my perspective
&lt;/h2&gt;

&lt;p&gt;I started googling around for alternatives. Eventually I stumbled across &lt;a href="https://carbone.io" rel="noopener noreferrer"&gt;Carbone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What caught my attention wasn’t even the API. It was the idea behind it.&lt;/p&gt;

&lt;p&gt;Instead of trying to force HTML into behaving like a print layout engine, they just let you design documents in Word.&lt;/p&gt;

&lt;p&gt;And honestly, that makes a ton of sense.&lt;/p&gt;

&lt;p&gt;Word has spent decades solving pagination, print layout, margins, page breaks, etc. Meanwhile I was knee-deep in HTML hoping the **** document is going to render neatly this time. &lt;/p&gt;

&lt;p&gt;I tried Carbone and the output was actually pretty good.&lt;/p&gt;

&lt;p&gt;Then I got to the pricing and concurrency limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;€29/month&lt;/li&gt;
&lt;li&gt;1000 renders&lt;/li&gt;
&lt;li&gt;only 2 concurrent documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was kind of the moment where I thought (where we have &lt;em&gt;all&lt;/em&gt; thought):&lt;/p&gt;

&lt;p&gt;“Honestly, I kinda want to try building this myself.”&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built one in Rust
&lt;/h2&gt;

&lt;p&gt;Originally this was not supposed to become a product.&lt;/p&gt;

&lt;p&gt;I just wanted PDFs for our inspection app.&lt;/p&gt;

&lt;p&gt;I built a very rough prototype in Rust using Handlebars-style templating and started experimenting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOCX templating&lt;/li&gt;
&lt;li&gt;image replacement&lt;/li&gt;
&lt;li&gt;loops&lt;/li&gt;
&lt;li&gt;conditionals&lt;/li&gt;
&lt;li&gt;PDF conversion pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The weird part is it actually started working surprisingly well.&lt;/p&gt;

&lt;p&gt;Like, suspiciously well.&lt;/p&gt;

&lt;p&gt;I expected this project to collapse under its own complexity almost immediately, but instead I kept adding features and the results kept getting better.&lt;/p&gt;

&lt;p&gt;And because I was building it specifically for property inspection reports, I was stress testing it with exactly the kind of PDFs that normally become nightmares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image-heavy documents&lt;/li&gt;
&lt;li&gt;cover pages&lt;/li&gt;
&lt;li&gt;tables&lt;/li&gt;
&lt;li&gt;long narratives&lt;/li&gt;
&lt;li&gt;repeated sections&lt;/li&gt;
&lt;li&gt;inconsistent data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point I realized:&lt;/p&gt;

&lt;p&gt;a) this was solving my own problem really well&lt;br&gt;
b) other developers probably hate this problem too&lt;/p&gt;

&lt;p&gt;So I turned it into a real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest thing I learned
&lt;/h2&gt;

&lt;p&gt;The actual PDF rendering isn’t the hard part. The hard part is everything around it. Especially images.&lt;/p&gt;

&lt;p&gt;If your document has 40 remote image URLs in it, that's 40 requests to download images that may be several MBs to even GBs, which if performed all at the same time, could run the instance out of memory during rendering.&lt;/p&gt;

&lt;p&gt;The rendering itself can be fast, but if the server has to fetch dozens of images over the network first, that becomes the bottleneck very quickly.&lt;/p&gt;

&lt;p&gt;I solved this by creating an buffer that parses for image URLs in your data and batches requests to pre-fetch them before rendering, which enables you to send those 40 images without crashing the instance.&lt;/p&gt;

&lt;p&gt;Another optimization that makes a huge difference was simply allowing images to be passed directly as base64 instead of URLs. That forgoes the issue altogether and removes a bunch of network overhead entirely.&lt;/p&gt;

&lt;p&gt;Right now I can render around 15 one-page PDFs/sec in production with simple templates (i.e. lots of data and &amp;lt;= 2 remote images).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I ended up making Rendrr
&lt;/h2&gt;

&lt;p&gt;The more I worked on this, the more I realized others could benefit from this.&lt;/p&gt;

&lt;p&gt;There are tons of HTML-to-PDF solutions.&lt;/p&gt;

&lt;p&gt;But if you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;truly WYSIWYG documents&lt;/li&gt;
&lt;li&gt;Word-based templates&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;decent performance&lt;/li&gt;
&lt;li&gt;sane pricing&lt;/li&gt;
&lt;li&gt;concurrency that isn't intentionally limited&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then there are surprisingly few options.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;a href="https://rendrr.io" rel="noopener noreferrer"&gt;Rendrr&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;(Did I just self-promote? Yes. Yes, I did. Sue me. I work hard and how else is anyone else going to know it exists if I don't talk about it?)&lt;/p&gt;

&lt;p&gt;Right now it’s still early access. I’m mostly focused on stability and making the core experience solid before I start piling on more features.&lt;/p&gt;

&lt;p&gt;That said, I do have some ideas I’m excited about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP server to generate and edit Word templates (&lt;a href="https://rendrr.io/docs/ai-template-skill" rel="noopener noreferrer"&gt;there is a skill already available though that does the same thing!&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;live previewing/editing in the dashboard&lt;/li&gt;
&lt;li&gt;maybe PowerPoint templating eventually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll see what users demand first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anyway
&lt;/h2&gt;

&lt;p&gt;This is my first SaaS that I've launched and would love to get some honest feedback about it. There's a free plan available. If you happen to hit your limit, feel free to reach out to me at &lt;a href="mailto:graham@rendrr.io"&gt;graham@rendrr.io&lt;/a&gt; and I'll gladly up your limit for the small exchange of telling me what you like/don't like so far.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>rust</category>
      <category>saas</category>
    </item>
    <item>
      <title>Raw SQL Migrations Library</title>
      <dc:creator>Graham Sutton</dc:creator>
      <pubDate>Fri, 13 Sep 2019 21:38:14 +0000</pubDate>
      <link>https://forem.com/grahamsutton/raw-sql-migrations-library-3a1h</link>
      <guid>https://forem.com/grahamsutton/raw-sql-migrations-library-3a1h</guid>
      <description>&lt;p&gt;Where I currently work, we operate on a stack of mostly PHP and PostgreSQL. Our framework was written in ZF1 (it's legacy), and our deployment process for keeping our database changes version-controlled is quite tedious and complex.&lt;/p&gt;

&lt;p&gt;Outside of work, I love using frameworks like Laravel and Rails and one of the features I really love from both of these is migrations. Since our legacy application at work doesn't have this concept, I started thinking of ways to take this modern approach and apply it to our legacy app.&lt;/p&gt;

&lt;p&gt;Since we cannot convert our entire application over to one of these frameworks (it would be a major overhaul), I looked into some options like Phinx and Flyway for possible integration, but these options were not really compatible with our app (for various reasons that would deserve their own post).&lt;/p&gt;

&lt;p&gt;At our company, we make very heavy use of PostgreSQL: complex procedures, triggers, views, multiple schemas, data warehousing, etc... we power everything on it. Many migration-type libraries usually have some abstraction layer that doesn't really support the full range of these database features. For example, Rails migrations are written in Ruby and then converted down to SQL before execution; same with Laravel and PHP. This isn't a bad thing, but these types of abstractions don't truly support the wide range of database features (particularly procedures) and are typically limited to just creating tables, indexes, and constraints.&lt;/p&gt;

&lt;p&gt;So, I decided to task myself (for fun mostly) with trying to build a migrations library that would run on raw SQL, and lo and behold, I actually got a simple concept going! The underlying CLI is written in PHP, but you write and execute migrations in raw SQL.&lt;/p&gt;

&lt;p&gt;I figured that maybe a lot of other people could get some use from such a library, and so I put it up on GitHub a while back but never really shared its existence with anyone. So today, that is what I am doing.&lt;/p&gt;

&lt;p&gt;I am really looking for people who would like to help make it better. I think it's a good start, but I know a truly great tool isn't built by one person alone. Feel free to criticize, praise, or contribute!&lt;/p&gt;

&lt;p&gt;Exodus Migrations Library: &lt;a href="https://github.com/grahamsutton/exodus" rel="noopener noreferrer"&gt;https://github.com/grahamsutton/exodus&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>sql</category>
      <category>database</category>
    </item>
    <item>
      <title>Laravel's factory method, but for JavaScript.</title>
      <dc:creator>Graham Sutton</dc:creator>
      <pubDate>Sun, 02 Jun 2019 14:40:08 +0000</pubDate>
      <link>https://forem.com/grahamsutton/laravel-s-factory-method-but-for-javascript-1o3a</link>
      <guid>https://forem.com/grahamsutton/laravel-s-factory-method-but-for-javascript-1o3a</guid>
      <description>&lt;p&gt;When I became exhausted with trying to create tons of mock API data on the fly for my Node projects, I created a simple little script that would let me define the skeleton of the JSON structure I wanted without having to copy, paste, and modify each individual record.&lt;/p&gt;

&lt;p&gt;This script is now available as an NPM package that does almost exactly what Laravel's factory method does except for JavaScript. It's lightweight and great for stubbing APIs in your Vue and React projects, too!&lt;/p&gt;

&lt;p&gt;Link to the repo: &lt;a href="https://github.com/grahamsutton/factory" rel="noopener noreferrer"&gt;https://github.com/grahamsutton/factory&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>vue</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
