Local First, but Not Local Only
In Excalidraw’s hosted application, “local-first” is not a slogan about avoiding servers; it is a concrete storage path for keeping a drawing available in the browser while the user works. This chapter traces what happens after an edit: structured elements and cleaned editor state go to one browser store, binary image data goes to another, writes are coalesced and sometimes paused, and load-time restoration rebuilds a usable scene. The result is continuity with explicit limits, not a guarantee that every value is instantly or transactionally durable.
That distinction matters because the previous chapter showed that a drawing is not one blob. The element list, AppState, and image payloads have different shapes and different consumers. Local-first behavior preserves that separation instead of forcing a browser storage API to treat a diagram and a collection of image bytes as the same kind of data.
Local-first starts with what is stored where
When the hosted application saves a local drawing, it writes non-deleted elements and a cleaned version of AppState to localStorage. The elements preserve the document: shapes, text, arrows, frames, images, and their relationships. The cleaned AppState preserves the parts of the editor that are useful when the drawing is reopened without blindly storing every transient UI condition. A user’s local viewport or preference can be restored where appropriate, while temporary selection or an open dialog does not become part of the shared document.
Binary files follow a separate path. The application uses an IndexedDB-backed store for image data, while image elements keep file IDs and statuses in the scene. This division matches the data. Structured elements and small state objects are convenient to serialize together; binary files can be larger, need independent retrieval, and benefit from being tracked and cleaned separately. The image element tells the renderer where the image belongs and which file it expects. IndexedDB supplies the file when the current browser session can retrieve it.
The distinction makes local recovery more precise. A reopened scene may contain a valid image element whose file is still being loaded. It may render a placeholder while the file manager fetches the payload. If the file is missing or an earlier save failed, the scene can remain present while the image status becomes an error. The drawing is not simply “saved” or “not saved”; its structured records and binary resources can be at different points in their lifecycles.
The app also keeps storage bookkeeping outside the scene itself. It marks when files were retrieved, tracks browser-state versions, and removes old unused files under its cleanup policy. This prevents every saved drawing from becoming a permanent archive of images that are no longer referenced. It also acknowledges that browser storage is a managed resource rather than an infinite disk. Cleanup is useful for hygiene, but it means a file that is no longer connected to a current scene may eventually disappear from the local store.
The local load path mirrors the split. The application reads serialized elements and AppState from localStorage, parses them defensively, combines stored state with current defaults, and returns an empty or partial result when a value cannot be parsed. Binary files are loaded through the separate file manager. The scene can therefore be restored first, then decorated with available image payloads as they arrive. This ordering is important for responsiveness: a missing image should not require the browser to discard an otherwise valid diagram.