Lesson 0004 · ~12 minutes
One skill: run a 60-second design-review scan — when OPFS is the wrong tool, and which production edges kill “just put it in OPFS.”
You can reject a bad OPFS proposal quickly (wrong store, user-visible confusion, “durable forever,” main-thread thrash, lock blindness) and green-light a good one with eyes open on quota and re-hydrate.
You’ve been lesson-first — fine. This page is self-contained for the quiz. Highest-value later skims: Storage for the web (quota / eviction / spectrum) and Persistent storage.
| Requirement | Usually not OPFS | Prefer |
|---|---|---|
| Drafts, entities, offline queues | Fake “files” for rows | IndexedDB |
| App shell / HTTP assets offline | Directory of Response clones | Cache API (+ SW) |
| Tiny theme / flag strings | Handle ceremony for a bool | Memory / careful Web Storage |
| User must open/save real disk paths | Private OPFS root | File System Access pickers |
| Multi-device source of truth | “OPFS will sync itself” | Server + explicit sync policy |
Lesson 1 still holds: OPFS wins for file-shaped content private to this origin — not as a general kitchen sink (web.dev).
OPFS is origin-private. Users do not browse it in the OS file manager. Confusing it with user-visible File System Access is a product bug, not a “permissions quirk” (web.dev OPFS).
navigator.storage.getDirectory() only.
move() OPFS entries into the user-visible tree; you
copy (main-thread picker + write) if the user needs
a real file.
OPFS counts toward the origin’s quota with IndexedDB, Cache Storage, and friends. By default storage is best-effort: under disk pressure, browsers may evict site data (often least-recently-used origins on Chromium/Firefox) (storage-for-the-web). Clearing site data / private modes wipe OPFS too.
navigator.storage.estimate() — approximate usage vs
quota; check usageDetails.fileSystem when present
(web.dev OPFS).
QuotaExceededError as a product path (delete old files,
ask the user, stop unbounded growth).
navigator.storage.persist() — request
persistent storage
for critical local data (permission/heuristic dependent; user can
still clear site data)
(persistent-storage).
Caching every export, every media take, every temp render “forever” is a quota bomb. Cap retention, namespace folders by generation, and delete on purpose — the browser will not be your product LRU.
Huge async writes/reads still compete with rendering. For heavy byte pipelines, plan a dedicated worker (lesson 3). Don’t invent “sync on main for speed.”
Default
sync access handle
is exclusive. Multi-tab / multi-worker writers fail until
close() — design ownership, not hope.
DevTools OPFS inspection is still uneven; Chrome’s OPFS Explorer extension is a practical workaround (web.dev). Budget time for it.
OPFS is a local origin store. Offline packages, conflict resolution, and “server always wins” are features you design. Shipping OPFS without answering who is truth when the network returns? is a deal-breaker for multi-device products — same as IndexedDB.
Wrong tool? · Private vs user-visible clear? · Survive eviction / clear-data / Safari? · Quota + delete plan? · Main-thread cost / worker path? · Lock ownership multi-tab? · Sync / re-hydrate explicit?
Equal-length options. Immediate feedback.
Proposal: store offline multi-paragraph drafts as named files under OPFS instead of keyed records.
Design review claim: “Once written to OPFS it can never disappear until we delete it.” Your response?
Product wants “Open project from disk / Save As…” into paths the user sees in Finder or Explorer.
Two tabs each open a default-mode sync access handle on the same OPFS DB file for writes.
Feature: multi-hundred-MB media re-encode into OPFS. First runtime shape to defend in review?
persist(), re-hydrate plan, Safari hostility.
web.dev — Storage for the web (quota, eviction, spectrum). Pair with Persistent storage when durability is the debate. Still useful deferred skim: the OPFS article’s worker + debug sections.