‹ Build a Dashboard Lesson 4 of 16
Contents Lesson 4 of 16

4 min read · professional

Panels as data, layouts as documents

Course 2 turned filters into data so screens could be saved. Same move, one level up: turn panels into data so layouts can be.

The layout document

type Panel = {
  id: string;
  type: "watchlist" | "screen" | "backtest" | "quote";
  title: string;
  config: unknown;      // validated per type
  position: { x: number; y: number; w: number; h: number };
};
type Layout = { name: string; panels: Panel[]; version: number };

If a layout is a document, then everything you want follows without new mechanisms: save it, name it, restore it, share it, diff two of them, and revert a change you regret.

Hard-code the panels in JSX instead and every one of those needs its own feature.

type decides everything, so validate it

config is deliberately unknown in the type and must be validated per panel type on read, not trusted.

The document came from a database row that came from a browser, and course 2 already established the rule: input that reaches a privileged path is validated against an allowlist. A layout is exactly that — it decides which requests your server makes with your key. An unvalidated config on a quote panel is a request builder for a stranger.

Unknown type gets a placeholder panel saying so. Never crash the whole cockpit because one panel is from a newer version of your own code.

Version the document, migrate on read

You will change the panel shape. Write the migration when you change it, not later:

const MIGRATIONS = {
  1: (d) => ({ ...d, panels: d.panels.map(p => ({ ...p, title: p.title ?? p.type })) }),
};

Run migrations on read, save at the current version. It costs ten minutes at the time and it is the difference between changing your own format freely and being frightened of your own saved data.

Sensible defaults, and one escape hatch

A new user gets a default layout that works: watchlist, one screen, one chart. Nobody should meet an empty grid and a "+" button.

And one button that resets to that default. When a layout gets into a state the person cannot fix by clicking, the alternative to a reset button is a support conversation with yourself.

Try it now

Write the layout type and a default layout before building any panel. Then hand your assistant the type and ask for the grid that renders it — a generated component against a type you designed is a very different experience from asking for "a dashboard".