Lessons learned from improving performance, caching, and user experience in a production web application.
The Problem
When we set out to build SAGE (School of Ancient Geomantic Education), our focus was not initially on Progressive Web Apps.
SAGE combines traditional systems such as Western Geomancy and Indian Ramal Shastra with modern web technologies, educational resources, interactive calculators, and AI-assisted interpretation.
As the platform evolved, performance, reliability, and user experience became increasingly important concerns.
Our users access the platform from a wide variety of devices and network conditions. Some use desktop computers with fast broadband connections. Others rely on mobile networks where latency and bandwidth can vary significantly.
We wanted the experience to feel fast, responsive, and dependable regardless of connection quality.
That led us to explore:
- Service Workers
- Intelligent caching strategies
- Firebase Hosting optimizations
- Progressive Web App technologies
- Performance-focused UX design
The goal was not to create a fully offline oracle. AI-assisted readings continue to require backend services and network connectivity.
Instead, the objective was to improve performance, reduce unnecessary network requests, create a more resilient user experience, and make the platform feel more like a modern application than a traditional website.
This is the story of how we transformed a straightforward HTML + Firebase Hosting application into a faster, more polished Progressive Web App—without React, Next.js, or heavyweight build pipelines.
The Core Principle: Treat the Service Worker as a Router
Many PWA tutorials present caching strategies as isolated techniques:
- Cache-first
- Network-first
- Stale-while-revalidate
In practice, a production application usually needs several of them simultaneously.
The most useful mental model we found was to think of the service worker as a routing layer.
Different content types receive different treatment.
| Content | Strategy |
|---|---|
| Images, CSS, fonts, static JavaScript | Cache-first |
| HTML documents | Network-first |
| API requests | Pass-through |
| Configuration modules | Always fresh |
The important question is not:
Which caching strategy should I use?
The important question is:
Which caching strategy should this particular resource use?
Once we adopted that mindset, the service worker became dramatically easier to reason about.
Public Content and Private Content Are Different Problems
One mistake I see frequently in PWA discussions is treating all pages equally.
They aren't.
Some pages are:
- Public
- Anonymous
- Safe to cache
Others are:
- User-specific
- Session-aware
- Privacy-sensitive
The boundary matters.
For example, serving a stale public calculator page is usually harmless.
Serving cached user-specific content to the wrong session is not.
Our solution was simple:
- Public pages can be pre-cached.
- Session-dependent pages are excluded from pre-caching.
- Sensitive content is always fetched fresh.
The result is a much safer offline experience.
Never Cache the Service Worker
If there is one rule worth remembering, it is this:
Do not aggressively cache your service worker.
The service worker controls your entire update mechanism.
If the browser becomes stuck with an old service worker, every future deployment becomes harder.
Static assets can be immutable.
The service worker cannot.
Treat it as the control plane for your application.
Build an Escape Hatch
Eventually, every production application encounters one of these:
- A bad deployment
- Corrupted cache state
- An update bug
- A broken service worker
When that happens, users should not need to clear browser data manually.
We implemented a simple versioning mechanism that can:
- Detect an application version change
- Unregister outdated service workers
- Trigger a clean reload
Think of it as an emergency recovery procedure.
You may never need it.
When you do need it, you'll be glad it exists.
Why We Chose localStorage Instead of IndexedDB
This decision often surprises developers.
IndexedDB is usually presented as the "correct" storage solution for PWAs.
For large datasets, that's true.
For our use case, it wasn't.
The application only needed to store:
- Small pieces of user state
- Temporary workflow data
- Lightweight JSON payloads
Each payload was only a few kilobytes.
The benefits of localStorage were compelling:
- Simplicity
- Synchronous access
- No schema management
- Minimal implementation complexity
Could IndexedDB have worked?
Absolutely.
Would it have improved the user experience?
Not meaningfully.
Sometimes the simplest solution is the right solution.
The Most Overlooked Performance Problem: Fonts
Many performance discussions focus on JavaScript bundles.
In our case, fonts were the bigger challenge.
The application supports multiple writing systems, including:
- Latin
- Devanagari
- Arabic
- Japanese
- Chinese
Without careful loading strategies, typography can easily become the largest source of perceived latency.
Three techniques made the difference:
- Using
display=swap - Adding preconnect hints
- Caching font resources after the first visit
After the initial load, typography effectively became free.
Offline UX Is More Important Than Offline Technology
Most developers focus on the technical side of offline support.
Users don't care about your caching strategy.
They care about what happens when connectivity disappears.
When a page isn't available offline, users should never encounter a browser error screen.
Instead, provide:
- A branded fallback page
- Clear messaging
- A recovery path
- Consistent visual identity
The goal is not merely functionality.
The goal is preserving trust.
Fast Applications Need Feedback
An unexpected challenge emerged once everything was cached.
The application became extremely fast.
Page transitions often completed in under 100 milliseconds.
Users interpreted this as abrupt rather than responsive.
The solution wasn't optimization.
The solution was intentional motion.
Subtle micro-interactions:
- Entry animations
- State indicators
- Ambient visual feedback
made the interface feel more polished despite adding virtually no latency.
This was a reminder that perceived performance and measured performance are not always the same thing.
What We Learned
After deploying and maintaining the application, a few principles stood out:
1. Service workers are routing infrastructure
Treat them like routers rather than cache containers.
2. Not everything belongs in IndexedDB
Simple state often benefits more from simplicity than scalability.
3. Offline experiences are UX problems first
Caching is only the implementation detail.
4. Every PWA needs a recovery mechanism
Eventually something will go wrong.
Plan for it.
5. Fast interfaces still need visual feedback
Perceived quality matters as much as measured speed.
6. Simplicity scales surprisingly far
For many applications, a lightweight architecture can outperform a far more complex framework-based stack.
Final Thoughts
The modern web platform already provides most of the tools needed to build capable offline applications.
Service Workers, Cache Storage, localStorage, and modern browser APIs are remarkably powerful when combined thoughtfully.
The biggest lesson wasn't technical.
It was architectural.
Offline support works best when it is treated as a product requirement from the beginning rather than an enhancement added later.
When that happens, the result feels less like a website and more like an application that simply happens to run on the web.
A Note on Offline Functionality
The techniques discussed in this article focus primarily on performance optimization, caching, installability, and user experience improvements.
While certain assets and resources benefit from browser caching, AI-assisted readings and other server-dependent functionality continue to require network connectivity and backend processing.
The goal was to build a faster and more resilient web experience rather than a fully offline application.
What has been your biggest challenge building PWAs in production? I'd be interested to hear what strategies have worked (or failed) for you.
About the Project
SAGE (School of Ancient Geomantic Education) is a modern geomancy platform that combines traditional Western Geomancy and Indian Ramal Shastra with contemporary software engineering.
The platform provides:
- Free geomantic calculators and educational tools
- Daily oracle readings
- Premium AI-assisted geomantic consultations
- Support for multiple languages
- Offline-capable Progressive Web App functionality
Explore the project at dotsofdestiny.com and learn more about how ancient symbolic systems can be implemented using modern web technologies.
Top comments (1)