|
| 1 | +--- |
| 2 | +title: "App Builder Best Practices" |
| 3 | +description: "Practical tips and patterns for getting the most out of Teable App Builder." |
| 4 | +--- |
| 5 | + |
| 6 | +## Six Core Principles |
| 7 | + |
| 8 | +### 1. Define your data model in Teable first |
| 9 | + |
| 10 | +Teable App Builder works on top of your existing Teable tables — **your tables and fields are your schema**, and the AI reads them directly when generating UI and logic. |
| 11 | + |
| 12 | +So before you start building, define your data model clearly. The more precise your field types, links, and read/write paths are, the higher the quality of what the AI produces. |
| 13 | + |
| 14 | +### 2. Plan before you build |
| 15 | + |
| 16 | +Once your data model is in place, still resist the urge to jump straight into building the UI. Run a planning pass with the AI first. |
| 17 | + |
| 18 | +Say something like: *"Let's not write code yet — let's make a plan."* Describe the problem you're solving, the target users, and the rough feature set. Let the AI draft a structured proposal. Review it, adjust it, and only start building once you agree the plan makes sense. |
| 19 | + |
| 20 | +<Tip>A few extra minutes aligning on direction up front saves hours of rework later.</Tip> |
| 21 | + |
| 22 | +### 3. Start small and layer up |
| 23 | + |
| 24 | +Don't try to cram every feature into a single prompt. Describe the core functionality first, get a minimal working version running, then add one thing at a time: one interaction, one style tweak, one piece of logic. |
| 25 | + |
| 26 | +Verify each change before moving on. When something breaks, you only need to roll back one small change instead of starting over. |
| 27 | + |
| 28 | +### 4. Be specific, not abstract |
| 29 | + |
| 30 | +Descriptions like *"make it prettier"* or *"make the interactions feel more natural"* carry almost no information for the AI. |
| 31 | + |
| 32 | +Effective prompts are concrete: **which page, which area, what behavior you want, what you don't want**. Attaching screenshots or reference UIs helps a lot. |
| 33 | + |
| 34 | +<Tip>Treat your prompt like a brief for a smart person who knows nothing about your project. The more precise the instructions, the closer the output gets to what you imagined.</Tip> |
| 35 | + |
| 36 | +### 5. Diagnose first, fix second |
| 37 | + |
| 38 | +When the app behaves unexpectedly, resist the urge to tell the AI to *"just fix it."* Vague fix instructions push the AI into blindly patching things, often introducing new bugs along the way. |
| 39 | + |
| 40 | +A better approach is two steps: |
| 41 | + |
| 42 | +<Steps> |
| 43 | + <Step title="Ask the AI to analyze first"> |
| 44 | + Describe the symptoms and ask the AI to list likely causes and possible approaches — without touching the code yet. |
| 45 | + </Step> |
| 46 | + <Step title="Pick a direction, then implement"> |
| 47 | + Decide which explanation is most plausible, and tell the AI to proceed along that path. |
| 48 | + </Step> |
| 49 | +</Steps> |
| 50 | + |
| 51 | +<Warning>If several fix attempts in a row fail, roll back to the last known-good version and start fresh. It's usually faster than patching on top of patches.</Warning> |
| 52 | + |
| 53 | +### 6. Lean on version rollbacks |
| 54 | + |
| 55 | +Every conversation with the AI produces changes. The recommended rhythm: finish one feature module, confirm it works, then move on. **Don't juggle multiple unfinished features at once.** |
| 56 | + |
| 57 | +Later change broke something? Roll back to the last stable version and try again with a clearer prompt. |
| 58 | + |
| 59 | +## FAQ |
| 60 | + |
| 61 | +### What should I do about 429 (Too Many Requests) errors? |
| 62 | + |
| 63 | +<Warning> |
| 64 | +The Teable API is limited to **10 QPS** (10 requests per second). Apps generated by App Builder can hit 429 errors during normal use if request handling isn't optimized. |
| 65 | +</Warning> |
| 66 | + |
| 67 | +There are three broad strategies to address this: |
| 68 | + |
| 69 | +<CardGroup cols={3}> |
| 70 | + <Card title="Caching" icon="database"> |
| 71 | + Reduce duplicate requests |
| 72 | + </Card> |
| 73 | + <Card title="Pagination & batching" icon="layer-group"> |
| 74 | + Shrink per-request payloads |
| 75 | + </Card> |
| 76 | + <Card title="Debounce & throttle" icon="gauge-high"> |
| 77 | + Lower request frequency |
| 78 | + </Card> |
| 79 | +</CardGroup> |
| 80 | + |
| 81 | +Each section below lists common scenarios, the fix, and a reference prompt you can reuse. |
| 82 | + |
| 83 | +#### Strategy 1: Caching (reduce duplicate requests) |
| 84 | + |
| 85 | +<AccordionGroup> |
| 86 | + <Accordion title="Too many requests fired on page load" icon="bolt"> |
| 87 | + **Scenario**: A dashboard page has multiple charts, stat cards, and lists. Each component independently queries a different table, so the moment the page opens, concurrent requests exceed the limit. |
| 88 | + |
| 89 | + **Fix**: Cache data in app memory after loading (1–3 minute TTL is a good start), and serve cached data on subsequent views. Lazy-load below-the-fold components to stagger request timing. |
| 90 | + |
| 91 | + <Tip> |
| 92 | + **Reference prompt**: "Cache data locally after the page loads, with a 1-minute TTL. Don't re-request the API within the TTL window. Delay loading for non-critical components by 500 ms." |
| 93 | + </Tip> |
| 94 | + </Accordion> |
| 95 | + |
| 96 | + <Accordion title="Multiple components querying the same table" icon="copy"> |
| 97 | + **Scenario**: Three components on the same page each need data from the same table and each fires its own request — when one would have been enough. |
| 98 | + |
| 99 | + **Fix**: Centralize data fetching so the same dataset is loaded once and shared across components. |
| 100 | + |
| 101 | + <Tip> |
| 102 | + **Reference prompt**: "If multiple components need data from the same table, fetch it once and share it across all of them. Do not fire duplicate requests." |
| 103 | + </Tip> |
| 104 | + </Accordion> |
| 105 | + |
| 106 | + <Accordion title="Re-fetching on page navigation" icon="arrows-rotate"> |
| 107 | + **Scenario**: Users move back and forth between pages. Each return triggers a fresh fetch, even when nothing has changed. |
| 108 | + |
| 109 | + **Fix**: Within the cache TTL, reuse the previously loaded data instead of re-requesting. |
| 110 | + |
| 111 | + <Tip> |
| 112 | + **Reference prompt**: "When a user returns to a page, if it's been less than 1 minute since the last load, use the cached data. Do not re-request the API." |
| 113 | + </Tip> |
| 114 | + </Accordion> |
| 115 | + |
| 116 | + <Accordion title="Dropdowns loading huge option lists" icon="list"> |
| 117 | + **Scenario**: A dropdown shows every record from a table as an option. With many records, that single request alone is heavy. |
| 118 | + |
| 119 | + **Fix**: Convert it into a search-style picker — only fetch matching records after the user types. Alternatively, cache the option list. |
| 120 | + |
| 121 | + <Tip> |
| 122 | + **Reference prompt**: "Dropdowns should not load all options upfront. Switch to a keyword search that fetches matching records on input, with debounce applied." |
| 123 | + </Tip> |
| 124 | + </Accordion> |
| 125 | + |
| 126 | + <Accordion title="Cascading selectors chaining requests" icon="sitemap"> |
| 127 | + **Scenario**: Choosing one field triggers a load for the next level's options. Multi-level cascades rack up several requests per interaction. |
| 128 | + |
| 129 | + **Fix**: Preload the related data once and filter locally, or cache cascade data after the first load. |
| 130 | + |
| 131 | + <Tip> |
| 132 | + **Reference prompt**: "Cache cascading selector option data locally after loading. When the user changes a parent option, filter from the cache instead of re-requesting." |
| 133 | + </Tip> |
| 134 | + </Accordion> |
| 135 | + |
| 136 | + <Accordion title="Re-renders triggering duplicate requests" icon="repeat"> |
| 137 | + **Scenario**: Poor state management causes components to re-fetch data on every render. |
| 138 | + |
| 139 | + **Fix**: Trigger data fetching on specific events (initial mount, explicit user action), not on every render. Use caching as a safety net. |
| 140 | + |
| 141 | + <Tip> |
| 142 | + **Reference prompt**: "Only fetch data on first page load or explicit user actions. Do not re-fetch on re-renders — use cached data instead." |
| 143 | + </Tip> |
| 144 | + </Accordion> |
| 145 | +</AccordionGroup> |
| 146 | + |
| 147 | +#### Strategy 2: Pagination & batching (shrink per-request payloads) |
| 148 | + |
| 149 | +<AccordionGroup> |
| 150 | + <Accordion title="Lists or tables without pagination" icon="table-list"> |
| 151 | + **Scenario**: Loading all records at once produces a flood of API calls when the dataset grows. |
| 152 | + |
| 153 | + **Fix**: Paginate. Only fetch the current page's data. |
| 154 | + |
| 155 | + <Tip> |
| 156 | + **Reference prompt**: "Show 20 rows per page. Only load the next page when the user navigates to it. Do not load everything at once." |
| 157 | + </Tip> |
| 158 | + </Accordion> |
| 159 | + |
| 160 | + <Accordion title="Per-row fetching for linked data (N+1)" icon="link"> |
| 161 | + **Scenario**: After loading a list, you fetch linked-table details for each record one at a time. Loading 50 projects and then 50 owner lookups = 50 extra requests in an instant. |
| 162 | + |
| 163 | + **Fix**: Fetch all linked data in one batch, not row by row. |
| 164 | + |
| 165 | + <Tip> |
| 166 | + **Reference prompt**: "When loading a list, batch-fetch all linked data in a single request. Do not loop through records to fetch their linked information individually." |
| 167 | + </Tip> |
| 168 | + </Accordion> |
| 169 | + |
| 170 | + <Accordion title="Per-row writes during bulk updates" icon="pen-to-square"> |
| 171 | + **Scenario**: Bulk-updating multiple records by sending one update request per record instead of a single batch request. |
| 172 | + |
| 173 | + **Fix**: Use the batch update API to submit all changes in one call. |
| 174 | + |
| 175 | + <Tip> |
| 176 | + **Reference prompt**: "For bulk operations, merge multiple record changes into a single batch request. Do not send one update per record." |
| 177 | + </Tip> |
| 178 | + </Accordion> |
| 179 | + |
| 180 | + <Accordion title="API calls inside loops" icon="rotate"> |
| 181 | + **Scenario**: A `for` loop processes records one at a time, calling the API each iteration. |
| 182 | + |
| 183 | + **Fix**: Collect all the IDs first, then issue a single batch request. |
| 184 | + |
| 185 | + <Tip> |
| 186 | + **Reference prompt**: "Do not call the API inside a loop. Collect all required IDs first, then issue a single batch request." |
| 187 | + </Tip> |
| 188 | + </Accordion> |
| 189 | +</AccordionGroup> |
| 190 | + |
| 191 | +#### Strategy 3: Debounce & throttle (lower request frequency) |
| 192 | + |
| 193 | +<AccordionGroup> |
| 194 | + <Accordion title="Search or filters without debounce" icon="magnifying-glass"> |
| 195 | + **Scenario**: Every keystroke in a search box fires a request. Typing a 4-character query produces 4 requests. |
| 196 | + |
| 197 | + **Fix**: Debounce the input — wait 300–500 ms after the user stops typing before firing the request. |
| 198 | + |
| 199 | + <Tip> |
| 200 | + **Reference prompt**: "Debounce the search input. Only send a request 300 ms after the user stops typing. Do not send requests mid-typing." |
| 201 | + </Tip> |
| 202 | + </Accordion> |
| 203 | + |
| 204 | + <Accordion title="Rapid repeated user actions" icon="hand-pointer"> |
| 205 | + **Scenario**: Rapid-fire submit clicks, quick filter toggling, fast pagination — each action fires a request immediately. |
| 206 | + |
| 207 | + **Fix**: Apply debounce or throttle. Disable submit buttons until the request completes to prevent double-submits. |
| 208 | + |
| 209 | + <Tip> |
| 210 | + **Reference prompt**: "Disable the submit button after click and re-enable it once the request resolves. Debounce filter changes so rapid changes within 300 ms fire only one request." |
| 211 | + </Tip> |
| 212 | + </Accordion> |
| 213 | + |
| 214 | + <Accordion title="Overly aggressive form auto-save" icon="floppy-disk"> |
| 215 | + **Scenario**: Every field change saves immediately. Filling out a form can trigger a dozen writes. |
| 216 | + |
| 217 | + **Fix**: Switch to explicit save on button click, or debounce auto-save so it fires once after a pause in editing. |
| 218 | + |
| 219 | + <Tip> |
| 220 | + **Reference prompt**: "Do not save on every field change. Save on explicit button click, or auto-save once after the user has paused editing for 2 seconds." |
| 221 | + </Tip> |
| 222 | + </Accordion> |
| 223 | + |
| 224 | + <Accordion title="Polling intervals that are too short" icon="clock"> |
| 225 | + **Scenario**: Data refreshes every few seconds, producing sustained high-frequency traffic. |
| 226 | + |
| 227 | + **Fix**: Lengthen the poll interval to something reasonable (30 seconds or more), or switch to manual refresh. |
| 228 | + |
| 229 | + <Tip> |
| 230 | + **Reference prompt**: "Set the auto-refresh interval to 60 seconds. Add a manual refresh button so users can pull the latest data on demand." |
| 231 | + </Tip> |
| 232 | + </Accordion> |
| 233 | + |
| 234 | + <Accordion title="Multiple components polling independently" icon="timer"> |
| 235 | + **Scenario**: Several components on a page each set up their own poll timer. The combined load easily exceeds the limit. |
| 236 | + |
| 237 | + **Fix**: Centralize polling. Run one periodic fetch, then fan the result out to every component that needs it. |
| 238 | + |
| 239 | + <Tip> |
| 240 | + **Reference prompt**: "Don't let each component set up its own poll timer. Use a single refresh mechanism that fetches everything on a schedule and distributes the data to the components." |
| 241 | + </Tip> |
| 242 | + </Accordion> |
| 243 | +</AccordionGroup> |
0 commit comments