I got tired of graph-paper sites that print at the wrong scale, hide the PDF behind a signup, or slap a watermark on it. So I built one that's free, has no login, and prints true-to-scale.
The interesting part for me was the architecture: a single pure function buildPaper(config) emits geometric primitives (lines / circles / text), and everything is derived from it - the on-screen SVG preview, the in-browser PDF, and the build-time PDF and OG-image generation. No drawing logic is duplicated across output formats, so adding a new paper style is one renderer function plus about five one-line registrations, and it automatically gets a live preview, a downloadable PDF, an OG image, and SEO pages across every paper size.
Everything is true-to-scale: the SVG viewBox and the PDF are both in millimetres, so a 5 mm grid measures exactly 5 mm on paper when you print at 100%.
It's fully static - no backend, no database, no runtime. About 1,900 pages are prerendered to HTML, ~1,700 PDFs are pre-generated at build time (reusing the exact same draw logic as the browser), and it all deploys to a CDN as plain files.
Tech stack:
- Nuxt 4 / Vue 3 (script setup, TypeScript), Nitro static generation (nuxt generate)
- Tailwind CSS v4 plus scoped CSS with a small design-token system
- Paper engine: one pure buildPaper() -> primitives; one renderer per style. Single source of truth for preview + PDF + images
- Preview: pure renderPaperSvg() -> inline SVG baked into the prerendered HTML, so previews render with zero client JS
- PDF: jsPDF (dynamic import) for arbitrary custom configs in the browser; a Bun script reuses the same primitives to pre-build the static PDFs
- Social images: SVG -> PNG at build time with @resvg/resvg-js (1200x630 OG per style + full A4 sheets)
- Data-driven pSEO: ~40 styles across the full ISO A/B/C and US/Imperial sizes -> ~1,900 pages generated from one data table; JSON-LD (WebSite/HowTo/FAQ/Breadcrumb); selective noindex on low-demand size combos so thin pages don't dilute the site
- Tooling: Bun (package manager + native-TS runner for all the generators)
- Hosting: Cloudflare Pages, pure static (no worker)
There's also a custom editor (tweak spacing, color and margins, then export) and a virtual graph-paper canvas you can draw on, where the logical drawing space is decoupled from the pixel buffer so it stays crisp on any DPR.
One war story: because Cloudflare Pages serves pages at trailing-slash URLs, the client router saw /foo/ while my lookup table keyed on /foo - so pages rendered correctly server-side, then hydrated straight into a 404. Classic "works with JS off, breaks with JS on."
Happy to answer anything about the rendering pipeline or the static-generation setup.
is there a way to export the .svg so i can load them into a plotter to make the graph paper? Since you've expended all the effort to ensure proper sizing and whatnot. It even looks like it has the same margins as my plotter!
Very cool.
However, on Safari, in the Mac, I don't see any graph on the first two (standard North-South/East-West graph).
I do see the isometric and hexagonal ones, though. Maybe the lines are too thin/light.
This is a nice sharply pointed tool. Thanks for the nice supply of pre configured styles!
Cool! Thanks for creating and sharing this!
I got tired of graph-paper sites that print at the wrong scale, hide the PDF behind a signup, or slap a watermark on it. So I built one that's free, has no login, and prints true-to-scale.
The interesting part for me was the architecture: a single pure function buildPaper(config) emits geometric primitives (lines / circles / text), and everything is derived from it - the on-screen SVG preview, the in-browser PDF, and the build-time PDF and OG-image generation. No drawing logic is duplicated across output formats, so adding a new paper style is one renderer function plus about five one-line registrations, and it automatically gets a live preview, a downloadable PDF, an OG image, and SEO pages across every paper size.
Everything is true-to-scale: the SVG viewBox and the PDF are both in millimetres, so a 5 mm grid measures exactly 5 mm on paper when you print at 100%.
It's fully static - no backend, no database, no runtime. About 1,900 pages are prerendered to HTML, ~1,700 PDFs are pre-generated at build time (reusing the exact same draw logic as the browser), and it all deploys to a CDN as plain files.
Tech stack:
- Nuxt 4 / Vue 3 (script setup, TypeScript), Nitro static generation (nuxt generate) - Tailwind CSS v4 plus scoped CSS with a small design-token system - Paper engine: one pure buildPaper() -> primitives; one renderer per style. Single source of truth for preview + PDF + images - Preview: pure renderPaperSvg() -> inline SVG baked into the prerendered HTML, so previews render with zero client JS - PDF: jsPDF (dynamic import) for arbitrary custom configs in the browser; a Bun script reuses the same primitives to pre-build the static PDFs - Social images: SVG -> PNG at build time with @resvg/resvg-js (1200x630 OG per style + full A4 sheets) - Data-driven pSEO: ~40 styles across the full ISO A/B/C and US/Imperial sizes -> ~1,900 pages generated from one data table; JSON-LD (WebSite/HowTo/FAQ/Breadcrumb); selective noindex on low-demand size combos so thin pages don't dilute the site - Tooling: Bun (package manager + native-TS runner for all the generators) - Hosting: Cloudflare Pages, pure static (no worker)
There's also a custom editor (tweak spacing, color and margins, then export) and a virtual graph-paper canvas you can draw on, where the logical drawing space is decoupled from the pixel buffer so it stays crisp on any DPR.
One war story: because Cloudflare Pages serves pages at trailing-slash URLs, the client router saw /foo/ while my lookup table keyed on /foo - so pages rendered correctly server-side, then hydrated straight into a 404. Classic "works with JS off, breaks with JS on."
Happy to answer anything about the rendering pipeline or the static-generation setup.
is there a way to export the .svg so i can load them into a plotter to make the graph paper? Since you've expended all the effort to ensure proper sizing and whatnot. It even looks like it has the same margins as my plotter!