Architecture · Product philosophy · Cloudflare Pages

How We Built InvoiceCraftly: A Privacy-First Invoice Generator on Cloudflare Pages

The hosting choice mattered. The more important decision was to make the browser—not a user account or document database—the centre of the product.

InvoiceCraftly started with an ordinary frustration: creating a professional invoice is often a five-minute task hidden inside software designed for a much larger job. Before a user can send one document, many products ask for registration, onboarding, workspace setup, subscription decisions, and the upload of business and client information.

We wanted to test a different premise. Could the useful part of invoicing live directly in the browser? Could someone open an editor, add a logo, prepare the document, include a carefully reviewed payment QR code, and export the result without first creating an account or placing the document in our database?

The answer was yes—but only after treating “no signup” and “browser-local” as architectural constraints rather than marketing copy.

The core product principle The browser should be able to create, edit, save, render, and export a document without depending on an InvoiceCraftly account or server-side document store.

We chose constraints before choosing infrastructure

It is tempting to begin a new SaaS product by selecting a framework, database, authentication provider, billing system, and deployment platform. We started one level earlier by defining what the product should refuse to require.

  • No mandatory account for the core editor.
  • No server-side invoice or client database.
  • No upload step before a document can be exported.
  • No permanent backend dependency in the main editing workflow.
  • No privacy claim that the implementation could not support.

Those decisions removed several common SaaS components, but they did not remove complexity. They moved it. State management, document persistence, schema upgrades, export reliability, backup, error recovery, and browser compatibility became product responsibilities rather than database responsibilities.

That trade was worthwhile for InvoiceCraftly because the product is a focused document workspace, not an accounting ledger or collaborative ERP system. The architecture matches the job.

Static-first, not static-only

InvoiceCraftly is delivered as static HTML, CSS, JavaScript modules, fonts, sample assets, and structured content. The core editor executes in the user’s browser. Cloudflare Pages is the delivery plane, but it is not where invoices are created or stored.

“Static-first” is an important distinction. It does not mean the product is a brochure site or that server-side code is forbidden. It means static assets and browser execution are the default, while network services must justify their existence.

The production shape

Cloudflare Pages Serves the public site, editor modules, content pages, styles, assets, and fingerprinted production bundles.
User’s browser Holds document state, calculations, previews, local history, workspace data, QR rendering, and export preparation.
Selective edge code Handles bounded tasks such as contact delivery or other capabilities that genuinely require a trusted network boundary.
Local persistence Stores recent documents and settings in browser storage, with explicit backup and recovery paths for important work.
Export pipeline Produces PDF, PNG, SVG, CSV, and versioned backup data without uploading invoice contents to a document service.
Public content system Publishes guides, templates, tools, trust pages, metadata, structured data, sitemaps, and machine-readable product context.

Cloudflare Pages supports Git-connected builds and deployments, with branch and pull-request previews that do not replace the production deployment. That model fits a small product well: every branch can become a reviewable site, while the production branch remains the release boundary.

Cloudflare also treats static asset requests separately from requests that invoke Pages Functions. The platform’s documentation currently describes static asset requests as free and unlimited on both free and paid plans, while Function requests count against Workers usage. That economic distinction reinforces the architecture: keep the common path static, and use Functions deliberately.

The browser is the application runtime

The editor is built as native JavaScript modules grouped by responsibility: invoice schema, calculations, templates, persistence, structured export, PDF and SVG export, QR generation, utilities, product events, accessibility, and the local multi-document workspace.

A simplified view looks like this:

src/
├── app/                         # editor orchestration
├── components/                  # shared site chrome and UI
├── features/
│   ├── invoice-schema/          # document contract and validation
│   ├── invoice-calc/            # totals and monetary rules
│   ├── invoice-storage/         # browser storage adapter
│   ├── local-workspace/         # recent documents and backups
│   ├── templates/               # rendering layouts
│   ├── export/                  # PDF, PNG and SVG preparation
│   ├── structured-export/       # CSV and structured snapshots
│   ├── qr/                      # local QR rendering
│   └── accessibility/           # semantic and image guidance
└── engineering/                 # public engineering journal

The storage adapter is intentionally small. It serializes data into browser storage and exposes asynchronous methods so the rest of the application is not tightly coupled to one storage implementation. The surrounding workspace contract does the harder work: validating documents, maintaining an index, creating versioned backups, importing safely, and recovering from partial writes.

This is one of the first lessons we learned: saying “we use localStorage” is not an architecture. Persistence needs contracts, migrations, limits, recovery behaviour, and honest user guidance.

Browser-local does not mean risk-free A user can clear browser data, use a private window, switch devices, or hit a storage limit. The product therefore needs visible backup tools and recovery guidance, versioned export formats, and clear security and browser-local limitations explaining what is—and is not—recoverable.

Export reliability became a core engineering problem

An invoice editor is only useful if the exported document is dependable. A live preview can look correct while a PDF clips a footer, a font fails to embed, a logo loses resolution, or a QR code becomes too small to scan.

That led to dedicated export modules rather than a single “print the page” button. The application checks page fit, calculates PDF image placement, serializes SVG output, embeds required font resources, validates generated documents, and keeps structured exports separate from visual exports.

We also learned that export quality is not one feature. It is a collection of related guarantees:

  • The preview and exported file should tell the same visual story.
  • Document credits, footers, and optional sections should not be clipped.
  • Logos and QR codes need explicit dimensions and predictable rendering.
  • Structured data exports should use stable schemas and filenames.
  • Failures need user-readable explanations rather than silent errors.

For a browser-first product, export testing deserves the same seriousness that a traditional SaaS product gives to its API or database layer.

A static site still needs a production build pipeline

The source site is readable and modular, but production assets are fingerprinted. The build walks JavaScript dependencies, rewrites local module references, fingerprints JavaScript and CSS by content, copies public assets, injects a build revision marker into HTML, and emits an asset manifest.

Source module
  → resolve local imports
  → rewrite dependency paths
  → hash final content
  → write /_assets/js/...module.<hash>.js
  → update HTML entry points
  → emit manifest and immutable cache headers

This gives long-lived cacheability for content-addressed assets without turning the project into a framework-specific build. It also makes deployments easier to inspect: a production page contains a revision marker, while the manifest maps source assets to deployed assets.

Before that production build runs, separate checks validate generated SEO blocks, tool metadata, public social metadata, structured data, internal links, canonical URLs, the sitemap, and the public-content registry. Content pages are part of the release process rather than files added after engineering is “finished.”

What Cloudflare Pages did well

Cloudflare Pages did not design the product architecture for us. It did remove a meaningful amount of deployment and delivery work.

Capability How it helps InvoiceCraftly What still remains our responsibility
Git integration Builds and deploys from the connected repository. Reliable tests, clear branch discipline, and safe build commands.
Preview deployments Provides a reviewable URL for branch and pull-request changes. Preventing preview indexing and validating the right environment.
Static asset delivery Makes a static-first product practical without operating an origin server. Asset fingerprints, cache rules, bundle discipline, and performance budgets.
Pages Functions / Workers Adds narrowly scoped server-side behaviour without a dedicated server. Routing boundaries, usage limits, security, observability, and failure handling.
Custom domains Connects the production site to the InvoiceCraftly domain. DNS configuration, redirects, canonical consistency, and release verification.

The most valuable feature for our workflow is not raw edge performance. It is the tight feedback loop between repository branches, preview URLs, and production releases. A content change, template change, export fix, or SEO improvement can be reviewed as a real site before it reaches the main branch. Customer-visible changes are then recorded in the InvoiceCraftly product changelog.

The hardest parts were not the hosting choice

Cloudflare Pages made deployment simpler, but it did not solve the product-specific problems. The most time-consuming work has been in the boundaries between product promises and browser reality.

1. Making privacy language precise

“Your data never leaves your device” is a dangerously broad sentence for a modern website. Fonts, analytics, contact forms, optional network features, or external links can all create network traffic even when invoice contents remain local.

We use narrower language: document content is stored in the current browser and is not uploaded to an InvoiceCraftly document database. Network-dependent features are described separately. The public privacy and legal responsibility page documents those boundaries for users. That language is less dramatic, but more defensible.

2. Keeping local data trustworthy

Recent documents, duplicated invoices, backups, schema versions, and settings all need validation. A malformed import should not overwrite valid local work. A partial storage failure should not leave the workspace index pointing to a missing document. Local-first design reduces server custody but increases the importance of local integrity.

3. Making interactive software discoverable

The editor itself is intentionally marked noindex,follow. Search engines should discover useful public explanations, guides, invoice tools, templates and finished examples, document-type pages, security and limitation pages, and engineering articles—not dozens of stateful editor URLs.

That required a public-content registry, canonical route rules, structured data validation, a sitemap that matches the registry, machine-readable product context, and internal-link checks. Search visibility became an architectural concern because the core application route is not the content strategy.

4. Deciding when the server is justified

Static-first can become dogma if every server-side feature is treated as failure. The better rule is narrower: the core workflow should not depend on server-side document custody. Contact delivery, abuse protection, payment verification, licensing, or carefully bounded assistance may still need a trusted server boundary.

The test is whether a network capability solves a real problem that the browser cannot safely solve—and whether the product can explain exactly what is sent.

What we would do differently

If we were restarting the project with today’s knowledge, several decisions would move earlier.

  1. Version the local document and backup formats immediately. Schema evolution becomes real as soon as the first user saves meaningful work.
  2. Separate document data, layout definitions, and rendering logic sooner. This makes new templates and future layout flexibility easier to add without rewriting the data model.
  3. Build export regression tests earlier. Visual and structural export failures can hide behind a correct browser preview.
  4. Create a public-content registry before the content library grows. Canonicals, discovery parents, contextual links, and sitemap membership are easier to govern from the beginning.
  5. Treat error messages as part of the architecture. A local-first application must help users understand storage, export, import, and compatibility failures without a support agent inspecting server logs.
  6. Define the server boundary in writing. New features are easier to evaluate when the team can ask whether they preserve the browser-local core.

When this architecture is a good fit

A browser-local, static-first approach is not universally better. It works best when the primary value can be delivered on one device, collaboration is limited, central reporting is not the core job, and users benefit from avoiding account setup or server-side custody.

It is a poor fit when the product requires real-time collaboration, cross-device continuity without manual backup, authoritative shared records, background processing, complex permissions, or server-controlled compliance workflows.

The broader lesson Architecture should follow the smallest truthful product promise—not the largest stack a startup might eventually need.

What comes next

InvoiceCraftly will continue to add document utilities, structured import and export, more flexible layouts, regional payment formats, and optional paid capabilities. The design challenge is to expand usefulness without quietly turning the no-signup core into a conventional account-based SaaS product.

That means future features will continue to pass through the same questions:

  • Can this work locally?
  • Does it require a network boundary?
  • What exact data would leave the browser?
  • Can the core editor still work if the service is unavailable?
  • Can we explain the trade-off in one honest paragraph?

The result is not the simplest codebase in every dimension. It is a codebase aligned with the reason the product exists: professional invoicing without making people adopt invoicing software first.

Platform references. Cloudflare’s current documentation describes Git-connected deployments, branch and pull-request previews, Pages Functions, static asset and Function request pricing, custom domains, and extensionless HTML route behaviour. Platform details can change, so consult the current documentation before making infrastructure decisions.

See the architecture in use

Create a document without creating an account.

InvoiceCraftly creates invoices, quotes, estimates, receipts, and credit notes in the browser, with custom branding, optional reviewed payment QR codes, and downloadable exports.

Open the invoice editor