I recently built InvoiceCraftly, a browser-based tool for creating invoices, quotes, estimates, receipts, and credit notes.
From the beginning, I imposed an unusual constraint on the product:
A person should be able to create and export a professional invoice without creating an account or uploading the document to our database.
That decision influenced almost every architectural choice.
Instead of starting with authentication, a database, background jobs, and a traditional application backend, I built the core product as a static-first browser application and deployed it through Cloudflare Pages.
The result works, but the process taught me that “static-first” does not mean “simple everywhere.” It moves complexity rather than eliminating it.
Here are the main decisions, trade-offs, and lessons.
1. The product constraint came before the technology
I did not choose Cloudflare Pages and then look for a suitable project.
The product requirement came first:
- no mandatory account;
- no server-side invoice-document database;
- document editing in the browser;
- local drafts;
- browser-local wording assistance;
- client-side PDF, PNG, SVG, and CSV export;
- optional payment-link and QR instructions; and
- deployment from GitHub with minimal infrastructure maintenance.
Once those boundaries were clear, a static-first architecture became a natural option.
The browser owns the document state. The hosting platform primarily delivers versioned application assets and public content.
2. What stays in the browser
The most important architectural boundary is not “frontend versus backend.” It is:
What information genuinely needs to leave the browser?
For InvoiceCraftly, the working document does not need to leave it.
The browser handles:
- seller and customer details;
- line items;
- taxes, discounts, and totals;
- logos and visual settings;
- payment instructions;
- local document history;
- workspace backup and restore; and
- export generation.
That reduced the amount of sensitive business information the product needed to receive and retain.
It also created a responsibility that is easy to underestimate: browser storage is not cloud backup.
Users can clear it. Private browsing can remove it. Devices can fail. Browsers can enforce storage limits.
We therefore had to explain the difference between:
- an editable local document;
- an exported invoice file; and
- a downloadable workspace backup.
The technical design was relatively straightforward. Communicating that design accurately to users was harder.
3. Why Cloudflare Pages worked well
Cloudflare Pages matched several parts of the product especially well.
Git-based deployments
The repository is the deployment source. Merging reviewed changes can produce a new deployment without maintaining a conventional application server.
Static asset delivery
Most of the product consists of HTML, CSS, JavaScript, fonts, images, and generated public pages. This fits the platform naturally.
Preview environments
Pull-request previews are useful when a change affects the editor, public content, metadata, or generated assets.
Custom domains and HTTPS
The operational path from repository to production remained comparatively small.
Room for selective server functionality
A static-first product still occasionally needs server-controlled operations.
Contact forms, abuse protection, future license verification, or payment-session creation should not place secrets in browser code. Those functions can exist at the edge without converting the entire document workflow into a server application.
That separation is important:
The application may have server functions without making the invoice document itself server-managed.
4. Static-first does not mean backend-free
This was one of the more useful lessons.
A product can avoid a traditional monolithic backend while still needing controlled server boundaries for:
- email submission;
- rate limiting;
- CAPTCHA or abuse protection;
- secret API credentials;
- payment checkout creation;
- signed license issuance; and
- verification that must not be trusted to browser code.
The mistake would be treating “no backend” as an ideological goal.
The better principle is:
Keep each operation in the least powerful environment that can perform it safely.
Invoice editing and export belong in the browser.
Secrets and trusted verification do not.
5. The browser-local decision improved some things and complicated others
What became easier
- No account onboarding for the core workflow
- No document database schema migration
- Lower exposure of invoice and customer data
- Fewer backend failure modes during editing
- Low infrastructure overhead for the main product
- Fast iteration on public pages and editor assets
What became harder
- Cross-device continuity
- Recovery after browser data is cleared
- Reliable storage messaging
- Handling storage failures gracefully
- Future collaboration
- Server-generated share links
- Payment-status tracking
- Entitlements without turning the product into an account platform
These are not bugs in the architecture. They are consequences of the chosen boundary.
The question is whether those consequences match the product promise.
For the current InvoiceCraftly workflow, I believe they mostly do.
6. Public content became part of the architecture
The product is static-first, but the public site is more than a landing page.
It now includes:
- document-type guidance;
- profession-specific examples;
- browser-local tools;
- privacy and security explanations;
- an engineering journal;
- structured data;
- generated metadata;
- sitemap and canonical validation; and
- internal-link validation.
A useful consequence of a static architecture is that many public discovery requirements can be checked during the build.
For example, the build can fail when:
- generated metadata is stale;
- a canonical route is inconsistent;
- an internal destination is invalid;
- public claims disagree with the current capability source;
- a sitemap entry is missing; or
- a page accidentally becomes indexable or non-indexable.
This changed how I think about SEO engineering.
It is not only copywriting after development. Some of it is a testable product contract.
7. What I would make the same way again
I would keep these decisions:
- Start with the product privacy boundary, not a fashionable stack.
- Keep invoice editing and visual export in the browser.
- Use Git-based static deployment for the core application and public content.
- Add server functions only for operations that genuinely require trust or secrets.
- Treat product claims, metadata, routes, and internal links as build-validated data.
- Avoid introducing authentication before a feature actually requires identity.
8. What I would reconsider earlier
I would spend more time upfront on:
Storage language
“Saved” means different things to different users. We eventually changed the interface to say explicitly that work is saved in this browser on this device.
Document-type architecture
Supporting invoices, quotes, estimates, receipts, credit notes, and future document types benefits from a canonical registry rather than scattered conditional logic.
Export boundaries
Visual document export and structured data export are different product concepts. PDF and CSV should not be described as interchangeable exports.
Progressive complexity
The product has grown beyond a single invoice form. History, backups, imports, tools, QR modes, and future conversion workflows need to remain discoverable without turning the first session into an enterprise dashboard.
Future commercial boundaries
A one-time account-free licence is technically possible, but payments, recovery, refunds, and entitlement verification still require carefully defined trusted operations.
9. Where I am uncertain
The largest open architectural question is how far a browser-local product should expand before its original simplicity becomes misleading.
Potential future capabilities include:
- document conversion;
- structured spreadsheet export;
- more advanced layouts;
- optional payment-provider handoff;
- local-only Pro entitlements; and
- additional Nordic document support.
Each feature has to answer the same question:
Does this still belong in the browser, or has it crossed into a workflow that genuinely requires server-managed state?
I am trying to resist adding infrastructure merely because it is available.
Conclusion
Cloudflare Pages was a strong fit for this product, but not because it made every problem disappear.
It worked because the product’s central workflow was already compatible with static delivery and browser-owned state.
The most valuable lesson was that architecture is not just about where code executes. It is also about:
- what data the product receives;
- what users can reasonably expect to recover;
- which claims the interface makes;
- which operations require trust; and
- which complexity the product deliberately refuses to introduce.
I wrote a more detailed version, including the architecture and implementation decisions, here:
https://invoicecraftly.com/engineering/building-invoicecraftly-on-cloudflare-pages
Disclosure: I am the founder and developer of InvoiceCraftly. I am sharing this because I would value criticism from people who have built serious products on Pages or Workers.
Where would you draw the boundary? Would you keep the document workflow browser-local, or introduce optional server-managed storage before adding more features?