---
title: "PGlite vs SQLite Wasm vs DuckDB Wasm: Browser Databases in 2026"
author: "Nate Laquis"
author_role: "Founder & CEO"
date: "2029-05-30"
category: "Technology"
tags:
  - browser database
  - PGlite
  - SQLite Wasm
  - DuckDB Wasm
  - local-first architecture
excerpt: "Running a full database inside the browser used to be a novelty. Now it is the foundation of local-first apps that stay fast, work offline, and sync seamlessly when connectivity returns."
reading_time: "14 min read"
canonical_url: "https://kanopylabs.com/blog/pglite-vs-sqlite-wasm-vs-duckdb-wasm"
---

# PGlite vs SQLite Wasm vs DuckDB Wasm: Browser Databases in 2026

## Why Client-Side Databases Matter Now

For years, the browser was a thin rendering layer. You fetched data from a server, displayed it, and sent mutations back over the wire. Every interaction required a round trip. Every offline moment meant a broken experience. That model worked when apps were simple and connectivity was reliable, but modern expectations have outgrown it.

Users now expect instant UI responses, offline support, and real-time collaboration. Meeting those expectations with traditional client-server architecture requires aggressive caching, optimistic updates, complex state management, and a prayer that your WebSocket connection stays alive. It is a lot of accidental complexity for something that databases already solve well.

The alternative: put a real database in the browser. Not a toy key-value store, not a hand-rolled IndexedDB wrapper, but an actual SQL engine compiled to WebAssembly. Three serious contenders have emerged: PGlite (full PostgreSQL in Wasm), SQLite Wasm (the world's most deployed database, now in the browser), and DuckDB Wasm (columnar analytics engine). Each makes different tradeoffs, and picking the right one depends entirely on what you are building.

![Server room with blue lighting representing database infrastructure powering browser applications](https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800&q=80)

We have shipped [local-first applications](/blog/local-first-software-architecture-guide) with all three engines across dozens of client projects. This guide covers what we have learned: real bundle sizes, cold start benchmarks, storage limits, sync options, and the practical tradeoffs that documentation rarely mentions.

## PGlite: Full PostgreSQL in Your Browser

PGlite, built by ElectricSQL, compiles the actual PostgreSQL engine to WebAssembly. This is not a PostgreSQL-compatible query parser. It is PostgreSQL 16, running the same planner, the same executor, the same extension system. If your query works in Postgres on the server, it works in PGlite in the browser.

### Bundle Size and Cold Start

The compressed Wasm bundle weighs roughly 3.3 MB gzipped. That is significantly larger than SQLite Wasm (around 400 KB gzipped) and comparable to DuckDB Wasm (around 2.8 MB gzipped). Cold start time, meaning the time from first byte to first query, runs between 200 and 400 ms depending on device and network conditions. On a fast laptop with cached assets, subsequent starts drop to around 80 ms. On a budget Android phone, expect 500 ms or more on the first load.

That initial payload is the biggest objection teams raise, and it is a valid concern for marketing sites or content-heavy pages. But for data-rich applications where users stay in a session for minutes or hours (dashboards, project management tools, CRM interfaces), a 3 MB one-time download is invisible.

### What Makes PGlite Compelling

The killer feature is PostgreSQL compatibility. You get JSONB with indexing, CTEs, window functions, full-text search with tsvector, and even extensions like pgvector for vector similarity search. If your backend runs PostgreSQL (and statistically, it probably does), you share the same query dialect between client and server. No translation layer, no query quirks, no "this works in Postgres but not in my browser database."

PGlite persists data to IndexedDB by default through the idbfs virtual file system. It also supports OPFS (Origin Private File System) for better performance on Chromium browsers, where sequential read/write throughput improves by roughly 2x over IndexedDB. For in-memory usage (ephemeral queries, tests), you skip persistence entirely and get the fastest possible execution.

### Where PGlite Struggles

Write-heavy OLTP workloads expose PGlite's overhead. Simple INSERT operations take roughly 0.3 to 0.5 ms each in PGlite versus 0.05 to 0.1 ms in SQLite Wasm. For a single insert, you will not notice. For bulk loading 10,000 rows, PGlite takes around 3 to 5 seconds while SQLite Wasm finishes in under a second. The PostgreSQL engine is simply heavier, with more planner overhead, more WAL machinery, and more safety checks. That overhead buys you power and correctness, but you pay for it on every write.

## SQLite Wasm: Battle-Tested and Lightweight

SQLite needs no introduction. It powers Android, iOS, every Chromium browser, macOS, and roughly a trillion deployed instances worldwide. The official Wasm build, maintained by the SQLite team themselves, brings that reliability to the browser.

### Bundle Size and Cold Start

The gzipped Wasm binary is approximately 400 KB, making it the lightest option by a wide margin. Cold start is nearly instant at 30 to 80 ms. On resource-constrained devices (budget phones, older tablets), this difference matters enormously. Your app is interactive before PGlite or DuckDB have finished downloading their Wasm binaries.

### Storage Backends

SQLite Wasm supports multiple storage backends, and choosing the right one is critical for performance. The OPFS (Origin Private File System) backend using a dedicated Web Worker delivers the best results. Sequential writes are roughly 5x faster than the IndexedDB backend because OPFS provides synchronous file access within a worker context. The IndexedDB backend (sometimes called the "kvvfs" layer) works everywhere but adds serialization overhead on every page flush.

A practical note: OPFS requires a dedicated worker thread and does not work from the main thread. If your app architecture does not already use workers, you need to factor in the complexity of message passing between your UI and the database worker.

### Query Performance for CRUD

For typical CRUD operations (SELECT, INSERT, UPDATE, DELETE on normalized tables), SQLite Wasm is the fastest of the three. Simple SELECT queries on indexed columns return in under 0.1 ms. JOINs across 3 to 4 tables with proper indexing run in 1 to 5 ms for result sets under 1,000 rows. Write transactions batch efficiently. You can insert 50,000 rows in a single transaction in roughly 1.5 seconds.

SQLite's query planner is simpler than PostgreSQL's, which is actually an advantage for the browser. Less planner overhead means lower latency on straightforward queries. The tradeoff is that complex analytical queries (multi-way JOINs with aggregations, window functions, CTEs over large datasets) run slower than they would in PostgreSQL or DuckDB.

### Ecosystem and Tooling

The ecosystem around SQLite Wasm is the most mature. Libraries like sql.js (an older Emscripten build), the official sqlite3 Wasm build, wa-sqlite, and absurd-sql provide different integration approaches. For React and Next.js projects, [libraries like Drizzle ORM](/blog/sqlite-for-production-turso-litefs-d1) support SQLite dialects directly, giving you type-safe queries with minimal boilerplate.

## DuckDB Wasm: Analytics in the Browser

DuckDB is a columnar, vectorized analytical database. If SQLite is optimized for transactional workloads (many small reads and writes), DuckDB is optimized for analytical workloads (scanning large datasets, aggregating millions of rows, running complex GROUP BY queries). The Wasm build brings that analytical power to the browser.

### Bundle Size and Cold Start

The DuckDB Wasm bundle is approximately 2.8 MB gzipped, with an additional 1 to 2 MB for optional extensions (Parquet reader, JSON handling, spatial functions). Cold start runs between 150 and 300 ms. The total payload rivals PGlite, but DuckDB compensates with faster initialization once the binary is cached.

### Where DuckDB Wasm Dominates

Analytical queries are where DuckDB leaves the other two behind. Scanning a 1 million row dataset with a GROUP BY aggregation completes in roughly 200 to 400 ms in DuckDB Wasm compared to 2 to 5 seconds in SQLite and 3 to 8 seconds in PGlite. Columnar storage means the engine only reads the columns your query touches, and vectorized execution processes data in batches of 1,024 or 2,048 values at a time instead of row by row.

DuckDB also handles Parquet and CSV files natively. You can point it at a remote Parquet file, run a query, and get results without downloading the entire file. HTTP range requests let DuckDB fetch only the row groups and column chunks it needs. For dashboards that query pre-aggregated Parquet files stored on S3 or R2, this is transformative. Your "backend" becomes a static file host.

### Where DuckDB Wasm Falls Short

Transactional workloads are not DuckDB's strength. Single-row INSERT operations are significantly slower than SQLite because the columnar storage engine is optimized for bulk operations. If your app needs to insert one row every time a user clicks a button, DuckDB adds unnecessary overhead. It also lacks an IndexedDB persistence layer as polished as PGlite's or SQLite's. The community has built workarounds (serializing the database to a Uint8Array and storing it in IndexedDB), but they feel bolted on rather than native.

DuckDB Wasm also uses more memory at runtime. The vectorized engine allocates larger intermediate buffers, and a complex query over a 100 MB dataset can spike browser memory usage by 300 to 500 MB. On desktop browsers with 8+ GB of RAM, that is fine. On mobile Safari with its aggressive memory limits, it can trigger tab eviction.

![Code on a laptop screen showing database query logic for web application development](https://images.unsplash.com/photo-1555949963-ff9fe0c870eb?w=800&q=80)

## Head-to-Head Comparison: Performance, Storage, and Limits

Numbers matter more than narratives when choosing infrastructure. Here is how the three engines compare across the metrics that affect real applications.

### Query Performance Summary

- **Simple SELECT (indexed, single row):** SQLite Wasm at 0.05 ms, PGlite at 0.3 ms, DuckDB Wasm at 0.5 ms

- **JOIN across 3 tables (1K result rows):** SQLite Wasm at 2 ms, PGlite at 4 ms, DuckDB Wasm at 3 ms

- **Aggregation over 1M rows:** DuckDB Wasm at 250 ms, PGlite at 3,500 ms, SQLite Wasm at 4,200 ms

- **Bulk INSERT (10K rows, single transaction):** SQLite Wasm at 800 ms, PGlite at 3,200 ms, DuckDB Wasm at 1,500 ms

- **Full-text search (tsvector/FTS5):** PGlite at 5 ms (tsvector), SQLite Wasm at 3 ms (FTS5), DuckDB Wasm not natively supported

### Storage Limits

All three engines are constrained by browser storage quotas, not their own internal limits. Chrome allocates up to 60% of the device's free disk space per origin. Firefox allows up to 50% of total disk space. Safari is the most restrictive, capping origins at roughly 1 GB and sometimes evicting storage under memory pressure. In practice, you should design for a maximum of 500 MB per database to stay safely within all browsers' limits.

IndexedDB is the common persistence layer, and it behaves differently across browsers. Chrome's IndexedDB is backed by LevelDB and handles concurrent reads well. Firefox uses SQLite internally (yes, really) and performs consistently. Safari's IndexedDB implementation has improved dramatically since 2024 but still occasionally drops transactions under heavy write loads. Always test persistence-heavy features on Safari before shipping.

### Wasm Bundle Sizes at a Glance

- **SQLite Wasm:** approximately 400 KB gzipped, the smallest by far

- **DuckDB Wasm:** approximately 2.8 MB gzipped plus optional extensions

- **PGlite:** approximately 3.3 MB gzipped, the largest due to full PostgreSQL engine

If your application is already shipping a 2 MB JavaScript bundle (common for complex React apps with charting libraries), adding PGlite or DuckDB effectively doubles your total download. SQLite Wasm adds a negligible increment. For apps that prioritize time to interactive, especially on mobile, SQLite Wasm's size advantage is decisive.

## Sync Strategies: ElectricSQL, PowerSync, and cr-sqlite

A client-side database without sync is just a cache. The real value of local-first architecture comes from bidirectional sync: your browser database stays in sync with a server-side source of truth, handles conflicts gracefully, and lets multiple users collaborate on shared data. Three sync solutions have matured into production-ready options.

### ElectricSQL

ElectricSQL is the sync engine built by the same team behind PGlite. It syncs data between a server-side PostgreSQL database and PGlite (or SQLite) in the browser using a "shapes" API. You define which subset of your data to sync (for example, "all tasks belonging to the current user"), and Electric streams changes in real time over HTTP. The sync protocol handles partial replication, which means you do not need to download your entire database to every client.

ElectricSQL uses a last-write-wins conflict resolution strategy by default, which works for most applications. For collaborative editing scenarios where LWW is too coarse, you can layer CRDTs on top. The developer experience is clean: a few lines of configuration, a React hook to subscribe to live queries, and your UI re-renders automatically when synced data changes.

### PowerSync

PowerSync pairs with any PostgreSQL, MySQL, or MongoDB backend and syncs data to SQLite in the browser (or on mobile via native SQLite). It uses a Dart-based sync engine compiled to Wasm for the browser runtime. The key differentiator is PowerSync's "sync rules," a declarative DSL that defines per-user data subsets, row-level security, and conflict resolution in one configuration file.

PowerSync handles offline-first scenarios particularly well. Writes queue locally, sync when connectivity returns, and conflicts resolve server-side using customizable logic. For teams already using [PostgreSQL or MongoDB](/blog/postgresql-vs-mongodb) on the backend, PowerSync slots in without requiring you to adopt a new database or ORM.

### cr-sqlite

cr-sqlite takes a fundamentally different approach. Instead of a separate sync service, it embeds CRDT (Conflict-free Replicated Data Type) logic directly into SQLite as an extension. Every table you mark as "replicated" gets automatic merge semantics. Two clients can modify the same row offline, and cr-sqlite merges the changes deterministically when they reconnect, without a central server deciding the winner.

The tradeoff is complexity. CRDTs add storage overhead (roughly 30 to 50% more per replicated table) and require careful schema design. Not every data model maps cleanly to CRDT semantics. Counters, sets, and LWW registers work beautifully. Ordered lists and rich text require specialized CRDT types that cr-sqlite does not yet fully support. For apps where true peer-to-peer sync matters (collaborative tools, field data collection, multiplayer games), cr-sqlite is worth the investment. For simpler use cases, ElectricSQL or PowerSync will get you to production faster.

![Analytics dashboard on a monitor displaying real-time data synchronization metrics](https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&q=80)

## Integration with React and Next.js

Getting a Wasm database running in a "Hello World" demo takes an hour. Integrating it properly into a production React or Next.js application takes deliberate architecture. Here are the patterns that work.

### Worker Thread Isolation

Never run your database on the main thread. Every query, no matter how fast, risks blocking rendering and making your UI feel sluggish. All three engines support running inside a Web Worker or a SharedWorker. Use Comlink or a similar RPC library to expose a clean async API from the worker to your React components. The mental model becomes identical to calling a REST API, except the "server" is a worker thread in the same browser.

### React Hooks and Live Queries

PGlite ships with an official React hook package (@electric-sql/pglite-react) that provides useLiveQuery. You pass a SQL string and parameters, and the hook returns reactive data that updates whenever the underlying tables change. It uses PostgreSQL's LISTEN/NOTIFY internally to detect changes without polling.

For SQLite Wasm, libraries like wa-sqlite combined with a custom React hook give you similar reactivity. You register a callback on the database's update hook (sqlite3_update_hook), and any INSERT, UPDATE, or DELETE triggers a re-query of affected live queries. The pattern requires roughly 50 lines of custom hook code, but it works reliably.

DuckDB Wasm is less natural in a React context because it is designed for one-shot analytical queries rather than reactive data binding. You can build a live query layer on top, but it fights the engine's design. If your React app is primarily CRUD with occasional analytics, use SQLite or PGlite for the reactive data layer and spin up DuckDB only for analytical queries on demand.

### Next.js Considerations

Next.js adds complexity because of server-side rendering. Wasm databases only exist in the browser, so any database initialization must be guarded with typeof window !== "undefined" checks or wrapped in dynamic imports with ssr: false. The App Router's server components cannot access client-side databases at all, which means your data-fetching architecture splits into two paths: server components fetch from your backend API, and client components query the local database.

A clean pattern is to hydrate the local database during initial page load using data fetched by server components, then switch entirely to local queries for subsequent interactions. This gives you the SEO benefits of server rendering with the performance benefits of local-first reads. The initial render comes from the server. Everything after that is instant, local, and works offline.

## Choosing the Right Engine for Your Use Case

After working with all three engines across production applications, here is our straightforward recommendation.

### Choose SQLite Wasm If...

You are building a CRUD application that needs offline support. Task managers, note-taking apps, form builders, inventory management, field data collection tools. SQLite's tiny bundle, fast cold start, and mature ecosystem make it the default choice. Pair it with PowerSync or cr-sqlite for sync, Drizzle ORM for type-safe queries, and you have a production-ready stack in a week. If you are unsure which engine to pick, start here.

### Choose PGlite If...

Your backend is PostgreSQL and you want query compatibility between client and server. If your team already thinks in PostgreSQL (JSONB, CTEs, window functions, full-text search), PGlite lets you reuse that knowledge on the client. It is also the best choice if you plan to use ElectricSQL for sync, since they are built by the same team and the integration is seamless. The larger bundle size is acceptable for complex apps where users spend extended sessions.

### Choose DuckDB Wasm If...

You are building analytics dashboards, data exploration tools, or any UI where users query large datasets interactively. Business intelligence tools, log viewers, financial modeling apps, scientific data browsers. DuckDB's columnar engine will run aggregation queries 10x to 20x faster than SQLite or PGlite on datasets above 100K rows. Combine it with Parquet files on cloud storage (Cloudflare R2, S3, GCS) and you can build a surprisingly powerful analytics frontend with no traditional backend at all.

### The Hybrid Approach

Nothing stops you from using two engines in the same application. We have built apps that use SQLite Wasm for the primary CRUD data layer (user profiles, settings, document metadata) and DuckDB Wasm for an analytics module that crunches usage data or financial reports. The two databases run in separate workers, use separate storage, and serve different parts of the UI. The combined bundle cost is around 3.2 MB gzipped, which is reasonable for a complex SaaS application.

The local-first movement is still maturing, and the tooling improves every quarter. Wasm database performance has roughly doubled in the last 18 months thanks to engine optimizations and browser improvements to OPFS and IndexedDB. If you evaluated these tools in 2024 and walked away due to performance or bundle size concerns, revisit them now. The gap between "browser database" and "server database" is closing faster than most teams realize.

If you are planning a local-first application and want help choosing the right database, sync strategy, and architecture for your specific use case, our engineering team has shipped dozens of these systems. [Book a free strategy call](/get-started) and we will map out the right approach for your product.

---

*Originally published on [Kanopy Labs](https://kanopylabs.com/blog/pglite-vs-sqlite-wasm-vs-duckdb-wasm)*
