How I Run Frontend Architecture Decisions: RFCs, ADRs, and Getting Team Buy-In
Every article on this blog shows a technical decision and its outcome. This one shows the process between them — how decisions are proposed, reviewed, challenged, and eventually committed to by the whole team.
The RSC migration I wrote about in September and the multisite architecture patterns from earlier this year did not emerge fully-formed from a single engineer's head. They were proposed, challenged, modified, and eventually ratified through a lightweight process that the team actually participates in. This article describes that process.
The goal is not process for its own sake. It is to make architectural decisions reversible when they turn out to be wrong, visible when someone new joins the team, and legitimate in the eyes of the people who have to live with them.
Why Informal Decisions Fail at Team Scale
At two or three engineers, informal decisions work fine. You talk, you agree, you build. The cost of reversing a bad decision is low because the blast radius is small.
At eight or ten engineers, informal decisions accumulate into a category of problem I call tribal knowledge debt: decisions that were made, but not recorded, by people who may no longer be on the team, for reasons nobody can fully reconstruct. You end up with a codebase full of load-bearing choices whose justifications have been lost, and a team that either religiously maintains patterns they do not understand or inadvertently undoes them.
The RFC and ADR processes fix different parts of this problem. The RFC process is forward-looking: it makes decisions deliberate before implementation. The ADR is a historical record: it captures why something was decided after the fact, for the benefit of future engineers.
The RFC Format
Our RFC template is intentionally short. An RFC that takes a day to write is an RFC that will not get written:
# RFC: [Title]
**Author:** [name]
**Date:** [date]
**Status:** Draft | Review | Accepted | Rejected | Superseded
## Problem
One paragraph. What is broken, slow, painful, or missing?
Do not describe the solution here.
## Proposed solution
What are you proposing to do? Include a rough implementation sketch
if the approach is non-obvious. Aim for 2–4 paragraphs.
## Alternatives considered
At least two alternatives. Why are you not choosing them?
This section demonstrates that you have thought about the problem,
not just your preferred solution.
## Trade-offs and open questions
What does this approach make harder? What would you need to validate
before being confident this is the right call?
## Success criteria
How will you know this worked? What would you measure or observe?
Running the Review
We run RFC reviews asynchronously by default. Synchronous RFC meetings have a bad failure mode: whoever speaks most confidently in the room wins, regardless of whether their position is correct. Async review gives quieter engineers time to think and write.
The process:
- RFC is opened as a pull request in the
docs/rfcs/directory - Author posts a link in
#frontend-engwith a two-sentence summary and a review deadline — typically five working days - Reviewers comment directly on the PR. Comments fall into three categories: blocking concerns, non-blocking suggestions, and explicit approval
- If there are no blocking concerns by the deadline, the RFC is accepted and merged
- If there are blocking concerns, the author either addresses them or calls a focused 30-minute meeting to resolve only the specific disagreement
The deadline is the most important part. Without it, async review becomes indefinite, and the RFC dies in review purgatory.
Architecture Decision Records
An ADR captures a decision that has already been made — often one that did not go through a formal RFC because it was made before the process existed, or because it was a small decision that seemed obvious at the time.
ADRs live in docs/decisions/ and follow a minimal format:
# ADR-0012: Use CSS Custom Properties for Tenant Theming
**Date:** 2025-03-15
**Status:** Accepted
## Context
We need to support per-tenant visual themes (brand color, typography)
without shipping separate CSS bundles per tenant. Inline styles were
evaluated but break pseudo-element support and prevent browser style sharing.
## Decision
Scope tenant tokens to a `[data-tenant="slug"]` attribute on the document root.
Components reference only semantic tokens (e.g., `--color-action-primary`),
never primitive tokens. Tenant CSS is generated at build time for static
tenants and injected inline in `<head>` for dynamic ones.
## Consequences
- Switching themes is a DOM attribute change with zero re-renders
- Requires a Stylelint rule to prevent components from referencing primitives directly
- CSS custom properties do not work in older IE (not a concern for our user base)
Handling Disagreement
The most common failure mode in RFC review is not technical — it is the engineer who leaves a blocking concern that is really a preference. The difference matters: a blocking concern prevents the proposal from working correctly; a preference is about how the author would approach it differently.
The framing I use with my team: a blocking comment should complete the sentence "this proposal will fail to solve the stated problem because..." If it cannot complete that sentence, it is a suggestion, not a blocker.
When genuine disagreement exists after async review, we use a structured approach:
- The author and the dissenting engineer each write their position in one paragraph — no more
- A third engineer who was not involved in the discussion reads both positions and asks the clarifying question that neither side has answered
- Usually, that question reveals the actual source of disagreement, which is often narrower than it appeared in the PR comments
Disagreement in an RFC is free. Disagreement after implementation is expensive. The process exists to make the cheap disagreements happen at the right time.
What This Process Is Not
It is not a governance layer. Every engineer can open an RFC. An RFC does not require sign-off from management. An RFC for a small, low-risk decision is two paragraphs and a five-day review window. The process scales with the decision, not the other way around.
It is also not a substitute for judgment. Not every decision needs an RFC. Adding a utility function does not need an RFC. Changing the data fetching pattern for all 400+ routes does.
Key Takeaways
- Informal decisions accumulate into tribal knowledge debt — decisions made but not recorded for reasons nobody can reconstruct
- RFCs are forward-looking (make decisions deliberate); ADRs are historical (capture why for future engineers)
- The "Alternatives considered" section is where proposals become credible — skip it and reviewers fill it in with doubt
- Async review with a hard deadline prevents both mob dynamics and indefinite purgatory
- A blocking comment must complete "this will fail to solve the problem because..." — otherwise it is a suggestion