Performance Guide · Bubble.io

Bubble.io Performance Optimization: 7 Patterns That Make Your App Fast

Slow Bubble apps are almost never Bubble’s fault. They are the builder’s fault. Seven specific anti-patterns cause 90% of all Bubble performance problems — and seven specific counter-patterns prevent every one of them.

90%of slowness is avoidable
<2sTarget page load time
7Patterns to master

⏱ 13 min read · Bubble.io · Performance Optimization 2026

Why Bubble Apps Get Slow — And Why It’s Almost Always the Builder

Bubble fetches data from a PostgreSQL database, runs it through a server-side engine, and sends it to the browser. When this feels slow, it is almost always because too much data is being fetched (wrong query approach), it is being filtered in the browser instead of the database (the :filtered by anti-pattern), or it is being fetched in the wrong place at the wrong time (missing caching, repeated count queries). Fix these three root causes and you fix 90% of Bubble performance problems.

The 7 Patterns That Separate Fast Bubble Apps From Slow Ones

Pattern 1 — Never Use :filtered by in Searches

❌ Slow Pattern
Search for Projects
:filtered by status = “Active”
// Fetches ALL projects from DB
// Filters in the browser
// Gets slower as data grows
✅ Fast Pattern
Search for Projects
status = “Active” ← constraint
// Runs as DB WHERE clause
// Returns only matching rows
// Fast at any data volume

Pattern 2 — Paginate All Large Lists

A repeating group trying to render 5,000 records will freeze the browser. Pagination with a page size of 15–25 ensures every list renders in milliseconds regardless of total data size. Use “Load More” or page number navigation — never unlimited lists in production apps.

// Repeating group settings
Type of content: Project
Data source: Search for Projects [constraints] :paginate(page: current_page, items per page: 20)
Load more button: current_page = current_page + 1

Pattern 3 — Use Option Sets for All Static Data

If you create a “Status” data type with five records (Active, Paused, Cancelled, Trialing, Expired), Bubble fires a database query to load those five records every time a dropdown or condition references them. An Option Set fires zero queries — the values are compiled into the app. For any fixed list, Option Set is always the correct choice.

Pattern 4 — Denormalise Dashboard Counts

// ❌ Dashboard queries count on every render (6 DB calls)
Projects count: Search for Projects[workspace=current]:count
Tasks count: Search for Tasks[workspace=current]:count

// ✅ Store counts on Workspace record — zero queries at render
Workspace: project_count (number), task_count (number)
On Create Project: Workspace’s project_count = project_count + 1
Dashboard reads: Current User’s current_workspace’s project_count

Pattern 5 — Always Constrain Searches to Workspace

Every search without a workspace constraint scans your entire dataset regardless of how many tenants you have. As your app grows from 10 to 1,000 workspaces, unconstrained searches grow 100× slower. Every single search in every workflow and page must have workspace = Current User’s current_workspace as the first constraint — without exception.

Pattern 6 — Avoid Deep Relational Chains

❌ 4 DB Round-Trips Per Row
Cell’s Order’s customer’s company’s address

Each → fires a new database query. In a 20-row repeating group this is 80 separate DB calls on every render.

✅ Zero Extra Queries
Denormalise: store customer_name and company_address as text fields directly on Order. Read them directly from the Order record with no chain traversal.

Pattern 7 — Soft Delete, Never Hard Delete

Add is_deleted (yes/no) to every data type. Set it to yes instead of destroying the record. Add is_deleted = no to every search. Benefits: undo capability, audit trails, data recovery, and compliance with data retention requirements. Hard-deleting records in a SaaS is a practice that always creates problems you cannot undo.

Performance Audit: Run This Before Launch

  • Zero :filtered by expressions — all filtering uses search constraints

  • All repeating groups have a maximum page size of 15–25 records

  • All static data (statuses, roles, categories) uses Option Sets, not data types

  • Dashboard counts stored as denormalised fields on Workspace, not queried at render

  • Every search has workspace = current_workspace as first constraint

  • No relational chains deeper than two levels in repeating group cells

  • Every data type has is_deleted (yes/no) field

  • Bubble app on Growth plan or above for production (dedicated server)

Is Your Bubble App Slower Than It Should Be?

We audit and optimise Bubble apps for performance. Most issues are fixable in a single session.

Book a Free Call →
See Our Work

Simple Automation Solutions

Business Process Automation, Technology Consulting for Businesses, IT Solutions for Digital Transformation and Enterprise System Modernization, Web Applications Development, Mobile Applications Development, MVP Development

Copyright © 2026