Web app best practices
Learn about the best practices for building performant web apps.
Retool enables you to build highly performant apps that scale with your needs. As your Retool apps become more complex and interact with an increasing number of data sources, it’s important to monitor performance and make improvements where needed.
Follow these practices to identify, resolve, and prevent performance degradation and keep your apps operating as efficiently as possible.
Refer to the best practices guide for queries to learn more about writing performant queries for your data.
Investigate external factors
A significant amount of variability in app performance may be triggered by factors outside of Retool. These factors can greatly affect the speed of app loading, query runtime, and more. Check your infrastructure for the following common issues:
- Device CPU: This metric can vary between devices, such as a high-performance desktop and a low-performance laptop. Use Speedometer to check the responsiveness of your browser.
- Network speed: Use SPEEDTEST to test your network speed.
- Browser extensions: Injecting JavaScript and adding or modifying network requests can slow performance.
Retool recommends the following specifications for the computer that you use to edit apps:
- Processor: Intel Core i5 or an equivalent AMD processor.
- Memory: 16 GB RAM.
- Network Speed: 25 Mbps or higher (for smooth performance in data-heavy apps).
Utilize built-in performance tooling
Retool's Debug Tools have app-specific suggestions for improving performance or accessibility and addressing errors. In particular, the Performance tab surfaces suggested app optimizations directly related to performance. It’s a useful tool to diagnose common problems that degrade performance and view relevant app stats per page.
The following sections include more details about the recommendations that Retool surfaces through the Debug Tools Performance Tab, including explanations for each problem and suggested solutions.
Large payload size: Reduce query payload
Fetching large amounts of data can slow down load time and increase memory usage, which is amplified by slower networks or mobile devices.
To improve payload size, Retool recommends:
- Fetch only what you need: Reduce query size by selecting only the fields required for the UI. Avoid
SELECT *
or fetching unnecessary nested objects. - Paginate large datasets: Break large datasets into smaller chunks using pagination. This reduces payload size and improves perceived performance by loading data incrementally.
- Cache payloads: Cache frequently accessed data to avoid redundant large payloads. This minimizes network calls and improves response times. Refer to the Query performance best practices for more information.
- Query data server-side: When possible, aggregate data on the server side and return only essential data you need.
Keep payload size below the following thresholds:
- Moderate performance impact: 1.6MB (the amount of data that can be theoretically downloaded on a 3G connection while still achieving a Time To Interactive (TTI) of 10 seconds or less.)
- Severe performance impact: 3MB
Long query runtime: Optimize query calls
Slow queries can delay app responsiveness and block other operations. Query runtimes are highly dependent on factors like network speed, database resource limits, and more.
To decrease query runtime, Retool recommends:
- Optimize query calls: Review your query structure. Ensure you have appropriate indexing, avoid unnecessary joins, and fetch only what you need.
- Paginate large datasets: Instead of retrieving all records at once, fetch data in smaller chunks using pagination. This reduces the load on the server and improves the user experience.
- Cache frequently used data: Use caching to reduce redundant query execution for frequently accessed data, minimizing runtime for repeat queries. Refer to the Query performance best practices for more information.
- Break queries into smaller subqueries: If possible, split a complex query into smaller, more focused queries that can run in parallel to reduce overall wait time.
Keep query runtime below the following thresholds:
- Moderate performance impact: 3s
- Severe performance impact: 5s
Parallelizable queries
When queries are executed sequentially without needing to be, they introduce unnecessary delays and slow down page interactivity.
To parallelize queries, Retool recommends:
- Evaluate dependencies: Review your queries and determine which ones truly depend on each other. Queries without dependencies can be run in parallel.
- Use a controller query: Write a JavaScript query to act as a controller that triggers multiple queries in parallel and waits for them to finish using
Promise.all()
.
High page load queries: Run queries on demand
Running too many queries on page load can drastically increase the time for your app to become usable by overwhelming the server and browser, which blocks user interaction.
Retool flags this as a warning when there are high page load queries and your app’s Time To Interactive (TTI) is high, signifying a likely correlation between the two. If this does not apply to your app, feel free to ignore this warning.
To decrease the number of queries that run on page load, Retool recommends:
- Run queries on demand: Evaluate and configure non-critical queries to run only when triggered by user interaction (such as a button click) rather than automatically on page load. Refer to Query performance best practices for more information.
- Prioritize essential data: Focus on loading only the data that’s immediately necessary for the user. Defer fetching secondary data until after the initial render.
Keep page load queries below the following thresholds:
- Moderate performance impact: 7 and TTI > 5s
- Severe performance impact: 10 and TTI > 7s
Large client-side tables: Move operations server-side
Large tables with thousands of rows of data are expensive to render and sluggish with filtering, which is all done client-side on the browser.
To decrease client-side table size, Retool recommends:
- Use server-side pagination: Loading only the results for a given view and fetching more as users navigate through the table reduces load size and time to interaction.
- Sort and filter data server-side: Apply filters and sorting directly in the query to reduce the dataset before it reaches the client.
- Fetch only what you need: Limit your query to retrieve just the columns and rows you need. Avoid loading hidden or unused data.
Keep table size below the following thresholds:
- Moderate performance impact: 1000 rows
- Severe performance impact: 5000 rows
Massive app/page size: Move operations server-side
Massive apps or page sizes can have high complexity that have higher load times and memory usage. Retool apps scale seamlessly with size, but if you are experiencing severe performance issues, consider the following recommendations.
To decrease app or page size, Retool recommends:
- Convert to a multipage app: Multipage apps are the best way to scalably build large apps with separate pages for the best user experience and performance. If you have a legacy single-page app, import it as a page in a multipage app.
- Split the app into smaller parts: Consider splitting large apps into smaller, more focused apps for specific sets of tasks.
- Reuse module components: Reuse components across your app with modules to reduce redundancy and make the app easier to maintain.
Long transformer runtimes: Move operations server-side
Running heavy transformations on the client side for large datasets can slow down page interactivity.
To decrease transformer runtime, Retool recommends:
- Move to server-side processing: Whenever possible, shift data transformations to the server side. Server-side processing is generally faster and more efficient, as it doesn’t use up the browser’s resources.
- Paginate data: Instead of processing large datasets all at once, break the data into smaller chunks and transform them in stages. This reduces the load on the client and makes the app more responsive.
- Optimize the transformer logic: If server-side transformation isn’t feasible, ensure your transformer logic is efficient. Minimize unnecessary loops or operations, and avoid transformations on large datasets unless absolutely necessary.
Keep transformer runtime below the following thresholds:
- Moderate performance impact: 200ms
- Severe performance impact: 500ms
Unused queries
Unused queries are not necessarily harmful to performance unless they run in the background on page load unnecessarily, and you don't use their results. However, it’s best to clean up unused queries to reduce tech debt.
Retool recommends removing unused queries. Regularly audit your app to identify and remove any queries that are not being utilized. This reduces unnecessary processing time and clean up your app’s logic.
Unused table data
Fetching more data than necessary for a large table can slow down page interactivity.
To eliminate unused table data, Retool recommends:
- Fetch only necessary data: Limit the data you retrieve by selecting only the columns and rows you need for a specific table.
- Exclude hidden column data: If you have columns that aren’t being used in the UI but are still part of the data set, make sure to exclude them from your queries. This reduces the amount of data being fetched and improves performance.
Legacy tables/lists: Upgrade components
Legacy components in Retool, like the Legacy Table or Legacy List View, are older versions that lack virtualization and may be missing newer features for your application.
Retool recommends upgrading to the most recent Table and List View versions. Retool offers newer, more efficient table components that are designed to handle data better and provide a smoother experience for both developers and users. Retool always recommends upgrading to the most modern components—Tables and List Views are especially important because of their large impact on application performance.
Unvirtualized lists or with self-referential dependencies
List View components are virtualized by default, optimizing performance by rendering only the items currently visible in view. The Enable instance values setting is an effective way to expose form values inside a List View, but it prevents proper virtualization because each item must be calculated and rendered up-front. The same occurs when a value and/or formDataKey
depends on another property inside the List View.
To address an unvirtualized List View, Retool recommends:
- Disable instance values: If your List View doesn’t require dynamic properties for each item, turn off Enable instance values in the Inspector editor to enable virtualization.
- Evaluate List View properties: Having one item’s property reference another item’s property within the same List View is a bad practice, because one item update forces the whole List View to update, preventing virtualization and is harder to debug and maintain.
- Break down large lists: If virtualization isn’t an option, consider breaking the List View into smaller chunks or using pagination to load smaller data sets at a time.
Mobile only: Large Custom/Collection Views: Move operations server-side
Large collection views with thousands of rows of data can be expensive to render and especially sluggish with filtering, which is all done client-side on the mobile device.
To address large custom or collection views, Retool recommends:
- Use server-side pagination: Loading only the results for a given view and fetching more as users navigate through the table reduces load size and time to interaction.
- Sort and filter data server-side: Apply filters and sorting directly in the query to reduce the dataset before it reaches the client.
- Fetch what you need: Limit your query to retrieve just the columns and rows you need. Avoid loading hidden or unused data.
Consider self-hosting Retool for your organization
You can self-host Retool on your own infrastructure. You can run Retool in your own virtual private cloud (VPC) or behind your virtual private network (VPN). This can allow the Retool backend to be located much closer to your data sources.