- Where does the data behind a dashboard live?
- By default in Cloudflare D1, the SQLite database bound to the generated app, with the schema in your repository. Reaching an existing database elsewhere is possible but deliberate: a Worker connects through an HTTP data API or through Cloudflare Hyperdrive rather than opening a pooled connection the way a long-running server would. Decide early which of those you are doing, because a dashboard designed against a local table reads very differently from one that pages a remote API.
- Why not just query the raw table for every chart?
- Because the cost grows with your success. Counting a year of events on each page load scans everything each time, and the query that felt instant during the build becomes the reason people stop opening the page. Write summaries into a rollup table keyed by metric, dimension, and time bucket, with a unique index on that combination. Recompute the current bucket often and older buckets never, and the dashboard reads a few dozen rows instead of a few million.
- Can this dashboard builder produce real-time dashboards?
- It depends what you mean, and the distinction is worth making because it changes the architecture. D1 has no change subscription, so a chart cannot be pushed to when a row lands. Polling on an interval covers most operational reporting and is what a generated dashboard does by default. Genuine push -- a value that moves the moment it changes -- comes from the Durable Objects template holding a WebSocket, which is a different starting point rather than a setting.
- Which chart should I use for which question?
- Lines for movement over time, bars for comparison across categories, stacked bars only when the total is as meaningful as its parts, and tables whenever the reader needs the exact figure rather than the shape. Avoid a pie chart above about four slices; nobody compares angles well. The app templates include a charting library, so the constraint is not what can be drawn but whether the drawing answers the question somebody opened the page with.
- How should filters and date ranges be implemented?
- As parameters bound by the query layer, never as text interpolated into SQL. Resolve relative ranges such as the last thirty days on the server so that two users in different timezones see the same reporting period, index the timestamp column you filter on, and push the filter into the query rather than fetching broadly and narrowing in JavaScript. A filtered view that transfers the whole dataset first is the second most common cause of a slow dashboard.
- How do I show a large result table without the page stalling?
- Page it, and prefer keyset pagination over offsets. Sort by an indexed column, remember the last value on the page, and ask for rows after it; offset pagination re-scans everything it skips, so page four hundred costs more than page one. Return a page of rows and a cursor rather than a total count, since counting a large filtered set is often more expensive than fetching the rows the reader can actually see.
- Can users export what they are looking at?
- CSV is straightforward: the worker generates it from the same query the chart used, with the filters applied, so the file matches the screen. PDF is not built in and usually is not worth adding -- it means either a rendering service or a client-side library, and most requests for a PDF are really requests for something printable or emailable. Ask which one before you build it. Export the filtered set, not the whole table.
- How do I control who sees which numbers?
- In the query, not in the component. Hiding a chart in the interface while the endpoint still returns the figures means the data is one network tab away. Attach the permission check to the data-access layer so the query is scoped by the authenticated subject before it runs, and treat aggregates carefully: a total across a filter narrow enough to identify one record still discloses that record, however it is labelled.
- What should the dashboard show before it has data?
- The truth, distinguishably. Loading, empty, and error are three different states that all render as a blank rectangle if you let them, and the difference matters to whoever is deciding whether to trust the page. Use a skeleton while loading, say plainly when a range returned no rows, and surface failures rather than showing a zero. A chart that displays zero when the query failed is worse than one that displays nothing.
- My dashboard got slow. What is usually the cause?
- In order: a missing index on the column being filtered or sorted, aggregation done in JavaScript that SQL could have done, one query per chart repeated per row of some list, and unbounded date ranges. Check the shape of the query before adding caching, because caching a slow query hides the problem until data grows again. When you do cache, cache the computed answer with a stated staleness rather than the raw rows.