The Default Assumption
The conventional architecture for a web application looks roughly the same regardless of stack. A frontend — React, Vue, plain JavaScript, it does not particularly matter — talks to a backend over HTTP. The backend talks to a database. The database is administered by someone, somewhere, and runs on hardware that someone is paying for. Authentication state lives on the server. User data lives on the server. The server is the single source of truth, and the frontend is a view onto that truth.
This architecture is not wrong. It is the right model for a great many problems, and the tooling that supports it is mature. But it carries a specific set of costs that become more visible the more applications you try to run at once. Each backend needs to be deployed somewhere. Each database needs to be administered. Each authentication service needs to be secured, monitored, patched, and paid for. Each application acquires a small ongoing operational tax that is invisible at the scale of one application and dominant at the scale of eighteen.
The AIUNITES network was built under a constraint that made the conventional architecture impractical: the entire network had to run, in production, with no per-site server cost. Eighteen separate Heroku dynos and managed Postgres instances would have made the network financially unviable from day one. The architecture had to flip the default assumption.
What Local-First Actually Means
The term "local-first" gets used in several overlapping senses. The version that applies to AIUNITES is fairly specific: the user's device is the primary source of truth for their data, and the network is involved only when explicitly needed for sharing or backup.
This is different from offline-first, which is mostly about graceful degradation when the network is unavailable. It is different from edge computing, which is about moving server logic closer to the user. Local-first means that the data lives on the device first and elsewhere only as a derived secondary copy. If the network never works again, the application keeps working. If the user wants their data on a different device, they take it with them, deliberately, in a format they control.
For AIUNITES, this manifests as three layers of state, in increasing distance from the device:
- localStorage — the actual database for almost all per-user state, per site.
- Backup and restore via JSON — the format the user uses to move data between devices, save it externally, or migrate to a different site.
- CloudDB — an optional shared synchronization layer for the cases where data genuinely needs to live somewhere accessible from multiple devices.
The shape of these layers, and what they cost in complexity, is the rest of this article.
localStorage as the Real Database
The decision to use the browser's localStorage API as the primary persistence layer is the architectural pivot that makes the rest of the system possible. Each AIUNITES site stores its user accounts, application state, and per-user data under a site-specific key prefix. The structure is simple enough to write out:
The [prefix] is the site name — videobate, aibyjob, redomy, and so on — which keeps multiple AIUNITES sites coexisting in the same browser without colliding. Each key holds a JSON-serialized value, parsed on read and stringified on write.
localStorage is not a sophisticated database. It does not support queries. It does not enforce schemas. It does not provide transactions. Reading a key returns a string; the application is responsible for everything more structured than that. The 5MB-per-origin limit, while substantial for most uses, is genuinely a limit and the application has to plan around it.
What localStorage does provide is the only thing that actually matters for this architecture: persistent, fast, reliable per-origin storage that survives page reloads and works without any server contact whatsoever. Every other capability the conventional architecture requires — authentication, authorization, query, joining, indexing — can be reproduced in front-end JavaScript at the scale most applications actually need.
Authentication without a server
The first user to register on a fresh AIUNITES site becomes the admin. The site stores that user record in [prefix]_users with an isAdmin: true flag. Subsequent registrants are regular users by default. Login is, structurally, a form that compares submitted credentials against the stored user list and sets [prefix]_currentUser to the matching user ID.
This is, deliberately, not a real authentication system. It is suitable for the actual use case of most AIUNITES sites: a single user (or a small known group) using the site as a personal tool, where the threat model does not include sophisticated attackers and the application's value is not gated by access control. It would be wholly inappropriate for a banking application or a multi-tenant SaaS, and AIUNITES does not pretend otherwise.
Several sites in the network are migrating from this localStorage-based authentication to a database-backed login system that uses CloudDB as the authentication store. That migration is described later. The point for now is that the simple model is sufficient for many actual applications, and the cost of building a "real" authentication system before you need one is high.
Backup and restore as the portability primitive
Because all of a user's state is in a small set of localStorage keys, exporting it is trivial: serialize the values to JSON, prompt the user to download the resulting file. Importing is the inverse operation. The format is documented; the user can edit it in a text editor; it survives any future version of any browser; it survives the user switching browsers entirely.
This is what data ownership looks like in practice. The user does not have to ask the application's vendor for an export. They do not have to use a third-party data portability service. The export is a button on the settings panel, the file is theirs, and the application has no leverage over them once they have it.
Several AIUNITES sites use this backup format as the implicit interchange format between sites in the network. A user's identity record can be exported from the hub site and imported into a product site, populating that site's user store with consistent data. The format is the integration.
The CloudDB Layer
Local-first does not mean the network is never involved. Some kinds of data genuinely need to live somewhere accessible from multiple devices: shared identity across the AIUNITES sites, cross-site analytics, configuration that should propagate from a central source, and backup-of-record for users who want network-resident copies of their work.
The AIUNITES CloudDB layer addresses these cases without reintroducing a per-site application server. The pattern uses a single Google Apps Script endpoint backed by Google Sheets as the storage layer. Each site embeds a small JavaScript module — js/cloud-database.js — that knows how to read and write rows in the shared sheet. The sheet is, structurally, a database table. The Apps Script is, structurally, a stateless API in front of it.
This sounds informal, and in some ways it is. Google Sheets is not a high-performance database. Apps Script has rate limits. Concurrent writes can produce ordering ambiguities that a real database would resolve through transactions. None of this matters for the actual workload, which is occasional writes, infrequent reads, and a total data volume that fits comfortably in a sheet.
What the CloudDB layer provides is a syncable, networked, multi-device backbone that the application can opt into when it needs to. The default mode is local-first; CloudDB is the escape hatch for the cases where local-first is genuinely insufficient.
The opt-in pattern
Every AIUNITES site that uses CloudDB also has an "online/offline" toggle in its settings. With the toggle off, the site behaves as a pure local-first application; CloudDB is not contacted. With the toggle on, certain operations — logging in, creating items, updating settings — mirror to CloudDB asynchronously after they complete locally.
This design has one important consequence: the CloudDB layer never blocks the user-facing operation. A login completes immediately against localStorage. The cloud sync happens after, and if it fails, the local operation is unaffected. The cloud is a backup of the truth, not the truth itself.
Where the Model Works Well
The local-first pattern is genuinely good for several categories of application that are common at the AIUNITES scale.
Personal tools. A site that helps one user track home renovation projects, like Redomy, has no inherent need for server state. The user's projects are theirs. Other users do not need to see them. The cost of running a server to host data the user is going to access only from their own devices is overhead, not value.
Educational and reference content. A site like AIByJob, which curates AI tools by profession, is mostly static content. The interactive layer — favoriting tools, building shortlists — is per-user and easily local. There is no scenario where one user's favorited list needs to be visible to another user.
Demonstrations and prototypes. Many AIUNITES sites are demonstrations of capabilities that could later become full products. The local-first model lets these sites ship without committing to a backend infrastructure that may turn out to be unnecessary if the demonstration finds a different shape than expected.
Static content with light interactivity. A press section, a webring, a portfolio of links, a notation editor that produces text the user copies elsewhere — all of these are well served by static hosting plus localStorage for any small amount of state the user generates.
Where the Model Breaks
The local-first model breaks in predictable ways, and being honest about those is part of using it well.
Multi-user collaboration. Anything where multiple users need a shared, consistent view of state is fundamentally not a local-first application. localStorage cannot represent a shared state, and CloudDB's eventual-consistency semantics are wrong for collaboration. A real collaboration application needs a real database with conflict resolution, and AIUNITES has no shame in using one when that is the actual requirement.
Cross-device continuity without explicit action. A user who edits something on their phone and expects to find the edit on their desktop, without doing anything in between, will be disappointed by a pure localStorage-based site. The CloudDB layer addresses this for sites that opt into it, but the default experience is per-device.
Server-side enforcement. Local-first authentication cannot enforce anything against a determined user. If the value of the application depends on users being unable to modify their own data — a leaderboard, a competitive game, a financial application — the model is unsuitable. Server-side validation is non-negotiable in those cases.
Storage limits. 5MB per origin is a lot of text but a small amount of media. Sites that need to store substantial binary data — images, audio, video — cannot reasonably use localStorage as the primary store. IndexedDB pushes the limit higher, and Cache API pushes it higher again, but for genuinely media-heavy applications, the right answer is server-side object storage.
The 18-Site Test
Running eighteen separate sites on this architecture has taught a few things that are worth recording for anyone considering a similar pattern.
Consistency is the discipline, not the framework
Without a framework imposing structure across the network, consistency comes from explicit shared patterns. Every AIUNITES site uses the same localStorage prefix scheme. Every site that uses authentication uses the same user record shape. Every site has a settings modal in the same place with the same fields. The sameness is enforced by template inheritance — new sites are forked from DemoTemplate, which carries the canonical implementations — not by a runtime layer.
This works well as long as the templates stay aligned, and produces drift when they do not. The network has, on occasion, had to do a sweep where every site's authentication module is updated to match a new canonical version. The sweep is unglamorous but tractable: it is a script that walks the eighteen repositories and applies the same patch.
The browser's storage limits are real
The 5MB-per-origin localStorage limit is enough for a personal application's worth of state. It is not enough for a site that wants to cache, say, a user's entire history of generated content alongside their settings and identity records. When a site approaches the limit, the right answer is to push secondary state into IndexedDB rather than fight localStorage.
"Persistence" is not the same as "reliability"
localStorage data persists across reloads, but it can be cleared by the user, by browser privacy controls, by extensions, by enterprise device management policies, and by a long tail of edge cases. A serious local-first application treats local storage as durable but not reliable, and pairs it with a backup-and-restore flow that the user is gently encouraged to use periodically.
The total cost of operating the network
Eighteen sites, at the time of writing, run on free GitHub Pages hosting. Custom domains cost approximately fifteen dollars per year per domain at the registrar. The publishing pipeline runs on the operator's existing machine. The CloudDB layer runs on Google Workspace's free tier for personal use. The total ongoing infrastructure cost of operating the network, excluding the operator's time, is approximately what eighteen domain registrations cost — roughly two hundred and seventy dollars per year, or fifteen dollars per site per year.
This is not a typical web operations budget. It is the natural consequence of a model where the user's device does most of the storage work, where static hosting handles delivery, and where the publishing pipeline replaces what would otherwise be deployment infrastructure.
What This Pattern Is Suitable For
Local-first is not a universal answer. It is the right answer for a specific shape of problem — applications where the data is genuinely the user's, where multi-user coordination is not the core requirement, and where the cost of running a backend is high relative to the value the application creates. For that shape of problem, the pattern is excellent. For other shapes, conventional architectures remain correct.
The AIUNITES network exists, in part, to demonstrate that this pattern is more applicable than the default assumption suggests. Many applications that would conventionally be built with backends do not actually need them. Recognizing which ones do and which ones do not is, increasingly, an architectural skill worth developing.
Build Local-First with Us
If your application is the kind that fits this pattern, we can help you build it. AIUNITES Consulting offers architectural review and implementation engagements for local-first systems on static hosting.
Talk to Us → More on the Stack →