pwa-storage-patterns
Implement data persistence patterns for Progressive Web Apps. Use when working with browser storage APIs, implementing localStorage or IndexedDB, or managing client-side data persistence.
$ 安裝
git clone https://github.com/wchklaus97/remind-me-pwa /tmp/remind-me-pwa && cp -r /tmp/remind-me-pwa/.cursor/skills/pwa-storage-patterns ~/.claude/skills/remind-me-pwa// tip: Run this command in your terminal to install the skill
SKILL.md
name: pwa-storage-patterns description: Implement data persistence patterns for Progressive Web Apps. Use when working with browser storage APIs, implementing localStorage or IndexedDB, or managing client-side data persistence.
PWA Storage Patterns
Overview
This skill covers data persistence patterns for Progressive Web Apps, including localStorage and IndexedDB usage, error handling, and data migration strategies.
Storage Options
localStorage
Best for: Small data (< 5MB), simple key-value storage, quick access
Limits:
- Chrome/Edge: ~10MB per origin
- Firefox: ~10MB per origin
- Safari: ~5MB per origin
IndexedDB
Best for: Large data (> 5MB), indexed queries, transactions, complex data
Limits: Typically 50% of available disk space
localStorage Pattern
Standard Load/Save Functions
use web_sys::window;
use serde_json;
fn load_reminders() -> Vec<Reminder> {
if let Some(window) = web_sys::window() {
if let Some(storage) = window.local_storage().ok().flatten() {
if let Ok(Some(data)) = storage.get_item("reminders") {
if let Ok(reminders) = serde_json::from_str::<Vec<Reminder>>(&data) {
return reminders;
}
}
}
}
Vec::new() // Safe fallback
}
fn save_reminders(reminders: &[Reminder]) {
if let Some(window) = web_sys::window() {
if let Some(storage) = window.local_storage().ok().flatten() {
if let Ok(json) = serde_json::to_string(reminders) {
let _ = storage.set_item("reminders", &json);
}
}
}
}
Error Handling Strategy
Load Errors: Return safe default (empty collection)
fn load_reminders() -> Vec<Reminder> {
// Try to load, fallback to empty
// ...
Vec::new() // Always returns valid data
}
Save Errors: Silent failure (non-critical)
fn save_reminders(reminders: &[Reminder]) {
// Try to save, ignore errors
// localStorage errors are non-critical
}
Storage Key Naming
Convention: Plural, lowercase, snake_case
const STORAGE_KEY_REMINDERS: &str = "reminders";
const STORAGE_KEY_SETTINGS: &str = "settings";
const STORAGE_KEY_CACHE: &str = "cache";
Integration with State
Initialize from Storage
#[component]
fn App() -> Element {
// Load from storage on mount
let mut reminders = use_signal(|| load_reminders());
rsx! { /* ... */ }
}
Save on State Changes
// After adding
on_add: move |reminder: Reminder| {
let mut new_reminders = reminders();
new_reminders.push(reminder);
reminders.set(new_reminders.clone());
save_reminders(&new_reminders); // Persist immediately
}
// After updating
on_toggle: move |id: String| {
let mut updated = reminders();
if let Some(r) = updated.iter_mut().find(|r| r.id == id) {
r.completed = !r.completed;
}
reminders.set(updated.clone());
save_reminders(&updated);
}
// After deleting
on_delete: move |id: String| {
let mut updated = reminders();
updated.retain(|r| r.id != id);
reminders.set(updated.clone());
save_reminders(&updated);
}
IndexedDB Pattern (Future)
Service Manager Pattern
pub struct StorageService {
reminders_db: RemindersDB,
settings_db: SettingsDB,
}
impl StorageService {
pub async fn init() -> Result<Self, StorageError> {
let reminders_db = RemindersDB::new("RemindersDB", 1).await?;
let settings_db = SettingsDB::new("SettingsDB", 1).await?;
Ok(Self { reminders_db, settings_db })
}
}
Specialized Database Service
pub struct RemindersDB {
db: IdbDatabase,
store: ObjectStore,
}
impl RemindersDB {
pub async fn write(&self, reminder: &Reminder) -> Result<(), StorageError> {
// Write to IndexedDB
}
pub async fn read_all(&self) -> Result<Vec<Reminder>, StorageError> {
// Read all from IndexedDB
}
pub async fn read_by_id(&self, id: &str) -> Result<Option<Reminder>, StorageError> {
// Indexed query by ID
}
}
Data Migration
Version Management
const STORAGE_VERSION: &str = "v1";
const STORAGE_KEY: &str = "reminders_v1";
fn load_reminders() -> Vec<Reminder> {
// Try current version
if let Ok(Some(data)) = get_storage_item(STORAGE_KEY) {
if let Ok(reminders) = serde_json::from_str::<Vec<Reminder>>(&data) {
return reminders;
}
}
// Try old version and migrate
if let Ok(Some(data)) = get_storage_item("reminders") {
if let Ok(old_reminders) = serde_json::from_str::<Vec<OldReminder>>(&data) {
let migrated = migrate_reminders(old_reminders);
save_reminders(&migrated);
return migrated;
}
}
Vec::new()
}
Best Practices
DO:
- ✅ Always provide fallback on load error
- ✅ Save immediately after state changes
- ✅ Use descriptive storage keys
- ✅ Handle serialization errors gracefully
- ✅ Use consistent key naming (plural, lowercase, snake_case)
- ✅ Document storage keys in code
DON'T:
- ❌ Don't panic on storage errors
- ❌ Don't store sensitive data in localStorage
- ❌ Don't store large binary data
- ❌ Don't forget to save after mutations
- ❌ Don't use
unwrap()on storage operations - ❌ Don't mix storage formats (always use JSON)
When to Use Each Storage
Use localStorage When:
- Data size < 5MB
- Simple key-value storage
- No complex queries needed
- Quick prototyping
- Small to medium datasets
Consider IndexedDB When:
- Data size > 5MB
- Need indexed queries
- Need transactions
- Large number of records (1000+)
- Need partial updates
- Complex data relationships
Resources
Repository

wchklaus97
Author
wchklaus97/remind-me-pwa/.cursor/skills/pwa-storage-patterns
1
Stars
0
Forks
Updated4d ago
Added1w ago