Sep 11, 2025·8 min read

Frontend decision record your team will actually update

Learn how to keep a frontend decision record short and useful by capturing state choices, caching rules, and data edges in one file per feature.

Frontend decision record your team will actually update

Why teams lose frontend decisions

Frontend decisions rarely disappear in one dramatic moment. They leak away a little at a time.

A choice starts in chat: "keep this filter in the URL." Another lands in a ticket: "cache results for a few minutes." A third ends up in a code comment that nobody reads again. A few weeks later, the code is still there, but the reason behind it is gone.

That's when the trouble starts. Small frontend choices feel harmless when you make them. Later, they decide how data moves, when screens refresh, and which part of the app owns the truth.

One team keeps user settings in local component state. Another part of the app copies the same settings into a global store. Then a request returns old data, the cache doesn't refresh when people expect it to, and nobody knows which value the UI should trust.

The pattern is familiar: stale data that looks fine until a customer refreshes, duplicated state spread across components and forms, unclear ownership between frontend and backend, and bug fixes that add even more rules.

These bugs usually don't come from one huge architecture mistake. They come from dozens of small calls nobody wrote down. Should this value live in the URL, local state, or a shared store? When does cached data expire? Which request updates the list after a mutation? Each answer looks minor on its own. Together, they decide whether a feature feels solid or flaky.

A short file per feature fixes more than most teams expect. It gives the team one place to record state choices, caching rules, and the data edges where one screen affects another. Not a long spec. Not a pile of diagrams. Just a small file a developer can scan in two minutes and update in one.

That's what makes a frontend decision record useful. It stays close to the feature, explains the decisions code alone can't show, and gives the next person something better than guesswork.

What to put in the file

Most teams make this file too big, then stop reading it. Keep it short enough to scan in about two minutes. One screen is a good limit. If someone needs meeting notes, design debate, or a longer history, put that somewhere else.

The file works best when every feature uses the same layout. That makes it easier to compare, easier to review, and much easier to update during a rushed release.

A simple structure is enough. Start with the scope: the page, flow, or component the file covers. Then record the state choices: what lives in local state, shared state, URL params, or on the server. Add data and cache rules that explain where data comes from, how long it stays fresh, and what triggers a refresh. Note the data edges too, especially loading, empty, error, permission, stale data, and conflicting updates. End with open questions, including the owner and the next review date.

Keep facts away from open questions. Facts should read like settled decisions. Open questions should read like work still in progress. When those two get mixed together, people start treating guesses like rules, and the UI drifts.

A billing page makes the difference obvious. The file might say invoice filters live in the URL, the selected row stays in local state, and invoice data refreshes after payment succeeds or after 60 seconds. If the team still hasn't decided how to handle failed optimistic updates, that belongs under open questions, not beside established behavior.

The layout matters because it exposes weak spots. When every file follows the same order, a new developer can scan three features quickly and spot gaps. If one file needs far more space than the rest, the feature is probably too broad and needs two records.

How to write one in five steps

A decision record only works if it stays short. One file per feature is usually enough. If it starts reading like a spec, people stop opening it and stop updating it.

Fill it in before the code spreads across components, hooks, and API calls.

  1. Start with one plain sentence about the feature goal. Write what the user is trying to do, not what the team plans to build. For example: "Users can save a draft invoice and finish it later." That line keeps later decisions tied to a real outcome.
  2. List the screens and actions that matter. Name the places where the feature appears and what the user can do there. Keep it tight: create, edit, retry, cancel, refresh. That gives the team one shared view of the flow without drawing a full diagram.
  3. Decide where each piece of state lives. Separate local UI state from shared app state and server state. A modal open flag can stay inside one component. A selected account might live higher up. Server data usually belongs in the data fetching layer instead of getting copied into extra stores.
  4. Add cache rules and data edges before you write much code. Note what data you fetch, what can stay fresh for a while, what you must refetch after a user action, and which events update related screens. If saving a draft changes both the editor and the list page, write that down now. Small notes here prevent most "why is this page stale" bugs.
  5. Review the file after the first working version. This step matters. The first pass is often wrong in two or three places because the real behavior only shows up once the feature runs.

That's enough for a useful record. If someone joins the feature a week later, they should understand the state decisions, caching rules, and data flow in a few minutes.

Write down state choices clearly

A decision record works when it names each piece of state and gives it one home. Most confusion starts when the same value could live in three places and nobody wrote down why one place won.

Component state is the smallest bucket. Use it for UI details that matter to one part of the screen, like whether a modal is open, which tab is active, or whether a tooltip is visible. If the user can leave the page and lose that value without harm, local state is usually the right call.

URL state is for choices users expect to keep when they refresh, share, or use the back button. Filters, sort order, page number, and selected views often belong there. A short reason helps. "Sort order lives in the URL because support can open the same view from a shared link" is enough.

Server state comes from APIs, so treat the server as the source of truth. The file should say which data you fetch, where you read it, and whether the UI can show stale data for a short time. Avoid copying API data into shared app state unless the feature has a clear need, such as merging server data with unsaved local edits.

Form state deserves its own note because teams often mix it with shared state too early. Draft input values, field errors, and dirty flags usually stay inside the form until submit. Shared state should stay small and boring. That's a good sign.

A simple note format works well: state name, where it lives, who reads it, and why that scope fits.

Take a search page. The query text inside the input can stay in form state while the applied query goes in the URL. The results list stays in server state. The open state of a filter panel stays in the component. Four choices, four short reasons, and the next developer doesn't have to guess.

Add caching rules people can follow

Add AI to Delivery
Build an AI-augmented workflow for code review, testing, and documentation that your team will use

Caching rules fail when they sound like advice instead of instructions. "Cache this for better performance" tells nobody what to do. The file should be specific enough that a new teammate can read it and make the same choice you would make.

Start with the first load. Say exactly when the app requests data. Does it load on page open, when a panel expands, after a user picks a filter, or only after they click "Search"? That one detail saves a lot of wasted requests and a lot of guessing.

Then set a freshness window in plain language. "Fresh for 30 seconds" is clear. "Short-term cache" isn't. If the screen refetches in the background, say that too. People need to know whether the UI should quietly update or wait for the next user action.

A good cache note answers a few basic questions: when the first request starts, how long the data stays fresh, which events trigger a refetch, how the UI changes after create, edit, or delete, and when the cache must reset instead of sticking around.

The mutation rule matters most because teams often handle it three different ways in the same app. After create, you might add the new item to the list at once and then refetch once. After edit, you might patch the changed record in place. After delete, you should remove the item right away unless the server can reject the delete and put it back.

Call out places where the cache should die on purpose. Sensitive account data should usually clear on logout. Search results tied to a one-time filter often shouldn't survive a full reload. Fast-changing numbers, temporary permissions, and step-by-step form drafts need extra care too.

A small example is enough: an orders table loads when the user opens the page, stays fresh for 60 seconds, refetches on manual refresh and window focus if stale, updates the edited row locally after a save, removes deleted rows right away, and clears all cached data on logout. If two people on the team would still implement that differently, the rule isn't finished.

Map data edges before they break

Most frontend bugs start at the edges. A value comes from an API, gets reshaped in the client, lands in local state, then another service reads it later. If nobody writes that path down, the team starts guessing.

The record should name every source a feature touches. That usually means the main API, any client store, browser storage, and outside services like auth, payments, maps, or analytics. Keep it plain: one short note for each source, what it sends, and where the feature reads it.

Track ownership, not just movement

Teams often write where data appears, then skip who owns it. That's where confusion begins. If the server owns the user profile, say so. If the form owns unsaved edits until submit, say that too.

Ownership cuts off a lot of pointless debate. When a value looks wrong, the team can check the right layer first instead of tracing the whole app. A cart badge might read from a client store while the checkout total still belongs to the server. Those are different responsibilities, and the file should say so.

A short note per value works: source of truth, where the UI reads it, who can change it, and when it syncs back.

Mark shape changes and failure states

Write down every place where data changes shape. That includes API fields renamed for the UI, date strings turned into Date objects, server enums mapped to labels, or several responses merged into one view model. These spots break quietly.

A small mismatch can create surprising bugs. Say a feature shows subscription status. The billing API returns trialing, the app store keeps isTrial, and the banner component expects trial. That doesn't look serious until one stale screen hides the wrong state.

Also name the states people forget until release week: loading, empty, error, and offline. Don't just list them. Say what the feature does in each one. Does it show cached data while refetching? Does an empty result mean "no items yet" or "your filters removed everything"? If the user goes offline, can they still read old data, or does the screen block?

When you capture these edges in one short file, feature documentation becomes much easier to trust. New teammates can scan it quickly and spot risky handoffs before they turn into bugs.

A simple example for one feature

Get Fractional CTO Support
Bring in senior technical help for product architecture, delivery workflow, and lean infrastructure

A product list gets messy fast. Filters live in one place, counts come from another, and one bulk edit can leave half the screen stale. A short decision record keeps those choices in one file so nobody has to guess later.

One feature file can be this small:

# Feature: Product list with filters and saved views

Goal
Users can filter products, save a view, bulk edit selected items, and share the current list by URL.

State
- Local state: filter panel open or closed, draft filter input, selected rows.
- URL query params: search, status, category, sort, page, savedViewId.
- Rule: only applied filters go into the URL. Draft input stays local until the user clicks Apply.

Data
- Server data: product list, total item count, filtered item count, saved views.
- The API owns the list and both counts.
- The client does not recalculate counts after filtering or bulk edits.

Caching
- Cache the product list by full query string.
- Cache counts by the same query string.
- After a bulk edit, refetch the current list and current counts.
- If the edit can change a saved view result, mark saved view queries stale too.
- Do not refetch while the confirmation modal is open.

Edge case
- If a product gets deleted while its details tab is still open, show "This product no longer exists".
- Disable edit actions in that tab.
- When the user goes back to the list, refetch once and clear any selected rows that no longer exist.

This works because it answers the questions that usually cause bugs. Where does each piece of state live? Which data comes from the server? When does the cache refresh?

The awkward edge case matters. If one person has a product open in another tab and someone else deletes it, the UI needs a clear rule. Without that rule, users click "Save" on something that's already gone and the app feels broken.

The file stays short on purpose. If it grows into a spec, people stop updating it. If it fits on one screen, they usually won't mind touching it.

Mistakes that make the file useless

A decision record stops helping the moment it tries to do too much. The file should settle everyday questions quickly: where state lives, what gets cached, when data refreshes, and which edge between screens or services can fail.

Teams usually break it in predictable ways.

The first mistake is turning it into a mini design doc. If the file takes ten minutes to scan, nobody will open it during a bug fix. Keep the scope tight to one feature and one set of decisions.

The second mistake is pasting framework docs into it. Your team doesn't need a React Query or Redux tutorial here. They need the local rule: what this feature stores, where it stores it, and why.

Another common problem is leaving cache invalidation as "decide later." That's how real bugs start. A stale profile, a cart count that lags, or a dashboard that never refreshes often traces back to one missing rule.

Teams also mix current rules with future ideas. "We may move this to server state later" belongs in a backlog, not beside today's behavior. When shipped logic and rough plans sit together, people read the wrong part.

Then there's the most ordinary problem of all: nobody updates the file after launch. The first version is often decent. Three small changes later, the code and the record disagree, and the file turns into fiction.

A short file stays useful when it draws a hard line between what exists now and what might happen later. If a future change matters, add a tiny open question at the end or track it somewhere else. Don't let speculation blur the rules developers follow today.

Cache rules need extra discipline. If the team knows a list refetches after create, updates optimistically on edit, and clears on sign-out, write exactly that. Five plain lines beat a vague promise every time.

Ownership matters too. One person should update the record in the same pull request that changes the behavior. If the feature ships on Friday and the file waits until next week, it usually never gets fixed.

A quick review checklist

Tighten Your Data Ownership
Stop guessing which layer owns the truth and set rules your developers can follow

Use this check before a feature ships and again after the first week of real use. The file only works if someone can scan it quickly, trust it, and change it without digging through old threads.

A short file is usually better than a complete file nobody reads. If the page feels heavy, cut old debate, keep the decision, and keep the reason in one or two plain sentences.

Ask a few direct questions.

  • Can a new teammate explain where the data comes from, where it lives, and what updates it?
  • Does each shared state value have one home?
  • Do the cache rules cover create, edit, and delete with clear behavior, not vague promises?
  • Does each data edge show the source, the owner, and any shape change?
  • Has someone updated the file after real usage exposed gaps?

A profile screen makes the gaps obvious. Say it shows a user card, a permissions panel, and an activity feed. If the record doesn't say who owns the permissions state or what happens after an admin removes a role, people will guess. They usually guess differently.

That's the point of the review. You aren't grading the writing. You're checking whether the file still matches the feature people use today. If it doesn't, fix the feature or fix the record that day.

What to do next

Pick one feature that's already moving. Not the biggest one, and not the messiest one either. Choose something active enough that the team will touch it this week, then write the first record for that feature.

Keep it short. One file is enough if it covers three things clearly: where state lives, how caching works, and which data edges the feature depends on. If writing it takes an hour, the file is probably too big.

Use the same file during planning, during the build, and during review. That reuse matters more than perfect wording. A file people open during real work stays alive. One that exists only for documentation doesn't.

Keep edits small. If a cache rule changes from 5 minutes to 30 seconds, update that line the same day. If a component stops owning local state and starts reading from shared state, change that note right away. Teams stop trusting feature documentation fast when it lags behind the code.

A simple habit helps: treat the record like part of the pull request. If the behavior changed, the file changes too. That's usually enough to keep state decisions visible without turning the process into paperwork.

If your team keeps repeating the same frontend architecture arguments, or you want a cleaner process for AI-augmented delivery, an outside review can help. Oleg Sotnikov at oleg.is works with startups and small teams on product architecture, delivery workflow, infrastructure, and practical AI-first development setups that teams can actually use.

Start with one feature, use one format, and update it in small steps. By the end of the week, your team should have one file that answers real questions instead of creating new ones.

Frequently Asked Questions

What is a frontend decision record?

A frontend decision record is a short file for one feature. It says where state lives, how data refreshes, and which edge cases the team already decided so people do not have to guess later.

When should we create one?

Write it before the code spreads across components, hooks, and API calls. You do not need a full plan first; you need enough to lock down state, cache rules, and data flow early.

How long should the file be?

Keep it to about one screen. If someone cannot scan it in two minutes, it will drift out of date fast.

What should we put in the file?

Start with the feature goal in plain language. Then record the screens and actions, where each piece of state lives, how data loads and refreshes, the risky data edges, and any open questions with an owner.

How do we document state choices clearly?

Give each value one home and one reason. Put modal state and other small UI details in local state, keep shareable filters in the URL, leave API data in server state, and keep draft form input inside the form until submit.

How should we write cache rules?

Use direct rules, not vague advice. Say when the first request starts, how long data stays fresh, what triggers a refetch, and how create, edit, or delete changes the UI.

Which data edges matter most?

Name the source, the owner, and any shape change. Also write what the feature does in loading, empty, error, offline, stale, and conflict states, because those gaps cause most of the confusing bugs.

Who should own and update the record?

One person on the feature should update it in the same pull request that changes behavior. If the file waits until later, the code and the record drift apart.

What makes a decision record useless?

Teams usually break it by turning it into a mini spec, mixing future ideas with shipped behavior, or leaving refresh rules undecided. A file also stops helping when nobody updates it after real usage exposes gaps.

How do we start without slowing the team down?

Pick one active feature and write the first version in under an hour. Reuse the same format during planning, build, and review, then adjust small lines as the feature changes instead of trying to make the first draft perfect.