React Forum
Blog & Guide Responses
Jan 12, 2023 · 04:00 PM

[Guide] Build an Admin Dashboard - implementation notes

Started by Morgan Patel
guideadminbusiness rendererpolicy in rendergenerated routesstored columnstriage summary

The Admin Dashboard guide leans into a "business renderer" posture: instead of hiding policy behind external layers, it merges UI and policy in render so the route tree stays readable and snapshot-friendly. I also liked that it stores derived surfaces (visible sections, table columns, inspector summaries) so the dashboard feels stable even while policy is changing.

Read the guide

If you implemented a business renderer, where did you keep the config table (data, code, JSON), and how did you prevent it from becoming untyped chaos? Did you store derived columns/visibleRowIds, or did you compute them in render and accept the churn? How do you keep policy decisions legible for debugging (especially readonly/hidden behavior)? What did you use as "snapshot-worthy" evidence for the inspector panel?

Comments (18)

Back to latest
Avery Chen
Jan 16, 2023 · 04:20 AM

Policy-in-render is controversial, but it made audits easier for us.

When policy lives in code you can snapshot, people stop arguing about what the rules are.

Marisol Vega
Jan 26, 2023 · 01:12 PM

The best thing about storing columns is stability.

If your columns are derived per render from policy, the table can "shift" under users when policy updates.

Dmitri Kline
Feb 09, 2023 · 09:20 AM

We kept the business renderer config as a typed table with explicit policy hooks:

ts
type SectionId = 'users' | 'billing' | 'flags' | 'audit';
type Mode = 'safe' | 'full';

type SectionDef = {
  id: SectionId;
  label: string;
  columns: string[];
  visibleWhen: (ctx: { mode: Mode }) => boolean;
};

export const sections: SectionDef[] = [
  { id: 'users', label: 'Users', columns: ['email', 'role'], visibleWhen: () => true },
  { id: 'billing', label: 'Billing', columns: ['plan', 'status'], visibleWhen: (c) => c.mode === 'full' },
  { id: 'flags', label: 'Flags', columns: ['key', 'enabled'], visibleWhen: () => true },
  { id: 'audit', label: 'Audit', columns: ['action', 'actor'], visibleWhen: () => true },
];

Then a derive step writes visibleSections and columns into the section document.

Keira Santos
Feb 22, 2023 · 09:02 PM

Counterpoint: merging policy and UI can make refactors painful because policy gets coupled to component structure.

We had to be disciplined about keeping policy functions small and "data-ish".

Morgan Patel
Feb 25, 2023 · 11:09 AM

Agree. We treated policy like view-model logic: it can reference route keys, but it shouldn't reach into component internals.

If policy needs DOM knowledge, it's probably the wrong layer.

Grace McCoy
Mar 13, 2023 · 05:30 AM

Legibility trick: render readonly/hidden decisions as evidence, even if it's just a tooltip or a debug string.

Otherwise it looks like a bug when a field is disabled.

Jasper Nguyen
Mar 28, 2023 · 07:18 PM

We made the inspector summary a derived key and stored it per row selection.

If you compute it live, you can get mismatches between list selection and inspector detail when selection changes quickly.

Camille Duarte
Apr 19, 2023 · 04:45 PM

A misunderstanding I had: I assumed the business renderer meant dynamic imports and magic.

It's really just: generate route components from a table so the tree stays consistent.

Owen Price
May 11, 2023 · 10:00 AM

We stored derived visibleRowIds because it stabilized pagination and selection.

If the visible list changes due to policy, you want that change to be explicit and traceable.

Benji Rios
Jun 04, 2023 · 02:21 AM

We also stored an audit trail of policy decisions as a string array (just for debug builds).

It helped answer "why is this hidden" without reading code in prod.

Nina Kapoor
Jul 09, 2023 · 11:58 PM

Docs tie-in: this guide aligns with "make behavior visible" more than anything.

Related: Testing and Debugging and Performance and Rendering (Best Practices).

Lina Ortega
Aug 27, 2023 · 05:14 AM

We used mode=safe|full in global route state like the guide suggests and it made demos easier.

You could share a URL and get the same policy posture instantly.

Sofia Valdez
Oct 18, 2023 · 06:36 PM

Counterpoint: if policy is in render, you need to keep it fast or the dashboard gets sluggish.

We memoized policy outputs and stored them in the doc so the render tree isn't doing heavy checks per cell.

Amina Farouk
Oct 23, 2023 · 10:05 AM

Same. We treated "policy outputs" like derived state: compute once, store, render from stored keys.

It made the table feel stable and it kept policy from being a perf wildcard.

Theo Bennett
Jan 02, 2024 · 04:12 AM

If you're worried about coupling, I found that naming helps.

If the policy function is named like a domain rule, you can move UI around without losing the meaning.

Tomas Rivera
Apr 22, 2024 · 12:02 PM

The guide made me re-think dashboards as "policy viewers" as much as "data viewers".

Once you accept that, policy-in-render feels like the natural place to put it.

Evan Brooks
Aug 12, 2024 · 09:19 AM

We had issues with generated routes and analytics (hard to name screens).

We fixed it by storing a derived screenName key and rendering it as evidence for events.

Salma Qureshi
Jan 17, 2025 · 09:33 PM

Business renderer is a weird phrase, but the outcome is good: route trees you can actually reason about.

Priya Liao
Jun 03, 2025 · 05:18 AM

The biggest payoff was that policy stopped being "some backend thing" and became a UI contract.

Once it's a contract, you can test it.

Hana Saito
Nov 23, 2025 · 02:12 PM

Late note: storing derived columns also helps with accessibility because the table headers remain stable.

If columns change unexpectedly, screen reader users get a really confusing experience.