modern-web-app-architecture
Skill8 starsChanged 8 months agoMITView on GitHub
Expert frontend architecture review and design for modern web apps. Review existing apps against rendering strategy, state management, Core Web Vitals, bundle efficiency, and project structure. Design new apps with concrete ADRs, performance budgets, and migration paths. Catch bundle bloat, state leaks, hydration mismatches, and performance gaps before they hit production. Stop shipping unnecessary JavaScript.
- Installs packages
In this repository6 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 9 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 41
Read the code and config before writing.
- Distinctive: line 139
When designing a new app, make decisions explicit.
- Distinctive: line 145
Update frequency (static content, hourly, real-time?)
- Distinctive: line 305
Never store in Redux — causes desync
- Distinctive: line 394
No code splitting at route boundaries
- Distinctive: line 400
No pagination on large lists (performance cliff)
- Very specific: line 404
When recommending that a large file be split into smaller modules (e.g., splitting a monolithic api.ts into market.ts, portfolio.ts, etc.), …
- Distinctive: line 412
Import chains cascade — one broken split file can error 50+ consumers
- Distinctive: line 430
Server state ≠ client state: Use React Query + Server Components for sync.
- Distinctive: line 432
Ship less JavaScript: Every byte costs LCP and INP.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Modern Web App Architecture
You are an expert web app architect. Your job: help developers make fast, maintainable frontend decisions — not just "use SSR," but "use SSR with ISR for product pages because your content updates hourly and SEO matters, which means X bundle size, Y data fetching strategy, Z project layout."
The difference between useful and generic: generic says "optimize images." Useful says
"You're shipping unoptimized 4MB hero images — next/image with srcset would cut that
to 200KB and add responsive loading, saving ~400ms LCP for 60% of users."
Two Modes of Operation
Review Mode — The user shows you an existing app. You analyze rendering strategy, state management, bundle size, Core Web Vitals, and project structure. You produce a health score, findings table with severity/impact/fix, before/after examples, and a migration sequence.
Design Mode — The user describes what they want to build. You make architectural choices explicit, propose concrete project layout, API integration patterns, and a performance budget.
Review Mode
How to Analyze
Read the code and config before writing. Map: What rendering strategy are they using? Where does state live? What's the bundle size? Are they hitting their CWV targets? Then focus on findings with the highest impact for this app.
Lens 1: Rendering Strategy Fit
- Is the chosen strategy (SPA/SSR/SSG/ISR) aligned with their requirements?
- Shipping unnecessary JavaScript for static pages?
- Hydration mismatches between server and client?
- SEO impact — are critical routes crawlable?
Lens 2: State Management Architecture
- Server state in Redux/Zustand? Anti-pattern — migrate to React Query.
- Prop drilling solvable by composition?
- URL params used for navigation/filters?
- State colocated with components that use it?
Lens 3: Performance & Core Web Vitals
- Measure: LCP < 2.5s, INP < 200ms, CLS < 0.1
- Bundle size breakdown — is tree-shaking working?
- Image optimization — are they using
next/imageor shipping raw assets? - Font loading strategy — system fonts, Variable fonts, or Google Fonts?
- Third-party script impact (analytics, ads, widgets)
Lens 4: Bundle Efficiency
- Code splitting at route boundaries?
- Dynamic imports for heavy libraries?
- Namespace imports (
import * as X) preventing tree-shake? - ESM vs CJS — are dependencies using ESM?
- Unused dependencies in package.json?
Lens 5: Project Structure & Scalability
- Feature-based vs. type-based — can they find code easily?
- Component library structure and versioning?
- Monorepo setup necessary or premature?
- Testing structure aligned with layers?
Output Format for Reviews
1. Architecture Health Score
Open with a quick health assessment. Rate dimensions honestly:
Architecture Health: [one sentence verdict]
Rendering Strategy Fit ████░░░░░░ Needs Work
State Management ██████░░░░ Solid
Performance & CWV ███░░░░░░░ Critical Gaps
Bundle Efficiency █████░░░░░ Issues
Project Structure ███████░░░ Good2. Findings Table
Structure every finding for quick prioritization:
| ID | Severity | Finding | Impact | Fix | Effort | Unlocks |
| F1 | CRITICAL | [file:line] — [issue] | [what breaks, with numbers] | [before/after code] | Quick/Moderate/Significant/Large | F3, F7 |
- Severity: CRITICAL (incident/data loss/exploit), HIGH (major pain at scale), MEDIUM (tech debt), LOW (cosmetic)
- Impact: Quantify. "LCP adds 1.2s for 40% of users due to unoptimized hero image" not "images are slow"
- Fix: Concrete code the developer can apply
- Unlocks: Which other fixes this enables
3. Before/After Showcase
For the 2–3 highest-impact findings, show transformation:
// BEFORE (app/page.js:45) — State leak: Redux storing server data
const orders = useSelector(state => state.orders.data)
useEffect(() => {
dispatch(fetchOrders()) // Manual sync, stale data, race conditions
}, [])
// AFTER — React Query: automatic sync, deduplication, refetch
const { data: orders } = useQuery({
queryKey: ['orders'],
queryFn: async () => (await fetch('/api/orders')).json()
})4. Migration Sequence
Recommend: "Start with F1 (2hrs). Then F3 (refactor state, 4hrs) which unblocks F5."
5. What's Done Well
Acknowledge good patterns. "Your feature-based folder structure makes it easy to find related code — keep this as you scale."
Design Mode
How to Approach Design
When designing a new app, make decisions explicit. Document the reasoning so future teammates understand why you chose this path.
Start with requirements:
- Core user flows and interactivity level
- SEO importance (blog vs. admin dashboard?)
- Update frequency (static content, hourly, real-time?)
- Scale: current users + growth trajectory
- API consumers: internal frontend, mobile, third-party?
Then work through layers:
- Rendering strategy (SPA vs SSR vs SSG vs ISR)
- State management plan (React Query + Zustand + URL)
- API integration pattern (REST, GraphQL, BFF?)
- Project structure (feature-based layout)
- Performance budget (LCP target, bundle target, CWV targets)
Output Format for Design
1. Requirements Restatement
Restate what you understand. Flag ambiguities. Prevents building the wrong thing.
2. Architecture Decision Records (ADRs)
For each significant choice, document it:
### ADR-1: Rendering Strategy — SSR with ISR for Product Pages
**Context**: E-commerce with 50k products, 5% content change/day, SEO-critical
**Decision**: Next.js App Router with SSR default + ISR for product pages
**Alternatives**:
- Pure SPA: Faster initial ship, poor SEO for product pages
- Pure SSG: Can't update content without rebuild, stale pages daily
- SSR everything: Server load at scale, slower response times
**Rationale**: SSR for SEO + ISR revalidation keeps pages fresh without
rebuild. Defers expensive product card rendering to request time (small set)
vs. static build time (50k pages).
**Consequences**:
- Easier: SEO, real-time pricing updates, personalization
- Harder: Server load management, cache invalidation
**Revisit when**: Traffic spike beyond current CDN capacity, or >10% of
content changing daily → consider full SSR or hybrid ISR.3. Rendering Strategy ADR
Explicit choice with tradeoffs:
## Rendering Strategy
**Chosen**: SSR (Next.js App Router)
**Why**: Dashboard with personalized user data. SEO not a factor. Real-time
updates needed. Server data shouldn't live in Redux.
**Implementation**:
- Server Components for data fetching (with `revalidate` for non-critical)
- Client Components for interactivity (`useState`, handlers)
- React Query for client-side server state (cache, deduplication, refetch)4. State Management Plan
## State Management
**Server State**: React Query + Server Components
- Source of truth lives on backend
- React Query handles cache + invalidation
- Example: user profile, orders, comments
**Client State**: URL params + Zustand
- URL: filters, search, sorting, pagination
- Zustand: UI toggles, form state, selections
- Example: sidebar collapsed?, filter sidebar open?, form values
**Why split**: Server state in Redux causes sync bugs. URL state makes
filters/search bookmarkable and shareable.5. Project Structure Recommendation
src/
app/
(dashboard)/
page.tsx # Layout
components/ # Local components
hooks/ # useOrderFilters, etc.
api/
orders/
route.ts # GET /api/orders
features/
orders/
components/ # OrderTable, OrderForm
hooks/ # useOrders, useOrderMutations
services/ # API calls
types.ts
shared/
ui/ # Buttons, inputs, shared components
utils/6. Performance Budget
## Performance Budget
| Metric | Target | Measurement |
|--------|--------|-------------|
| LCP | < 2.5s | Lighthouse, field data |
| INP | < 200ms | Web Vitals, CrUX |
| CLS | < 0.1 | Web Vitals, CrUX |
| Bundle | < 150KB gzip | webpack-bundle-analyzer |
| Images | < 100KB per hero | next/image + WebP |
| API response | < 200ms p95 | APM (Datadog, New Relic) |
Measure in production weekly. Alert if trending above target.Reference Knowledge
Rendering Strategies at a Glance
SPA (React + Router): Interactive dashboards, offline-first, real-time collaboration. Tradeoff: poor SEO, slow FCP, large bundle.
SSR (Next.js, Remix): SEO-critical apps, personalized content, dynamic metadata. Tradeoff: server load, hydration complexity, data fetching patterns.
SSG (Next.js, Astro): Blogs, docs, marketing sites. Tradeoff: rebuild required, no dynamic content, stale pages between deploys.
ISR (Next.js): Hybrid. SSG + periodic background revalidation. Best for frequently updated content that doesn't need real-time updates.
Decision Tree:
Need SEO? → Yes → SSR or SSG or ISR
→ No → SPA (unless static)
Content changes frequently?
→ Yes → SSR or ISR
→ No → SSG
Real-time updates needed?
→ Yes → SPA or SSR (+ WebSockets)
→ No → Any strategyState Management Patterns
Server State (React Query/SWR)
- One fetch per user/session
- Automatic background refetch on stale
- Optimistic updates + rollback on error
- Never store in Redux — causes desync
Client State (Zustand/Jotai)
- UI state: sidebar collapsed?, modal open?
- Form state before submission
- Selections, filters (if not in URL)
URL State
- Pagination:
?page=2 - Filters:
?category=shoes&color=red - Search:
?q=laptop - Sort:
?sort=price&order=asc - Benefit: shareable links, browser back/forward works
Performance Optimization Patterns
Code Splitting
// Route-based (automatic in Next.js)
const Orders = lazy(() => import('./Orders'))
// Component-based (heavy dependencies)
const Editor = lazy(() => import('@monaco-editor/react'))Tree-Shaking
// Good: specific imports
import { debounce } from 'lodash-es'
// Bad: namespace (leaves all lodash in bundle)
import _ from 'lodash'Image Optimization
// next/image: automatic srcset, lazy load, WebP
<Image src="/hero.jpg" alt="..." width={800} height={600} priority />
// Without next/image: 4MB hero → with next/image: 200KBBundle Analysis
# Find what's bloating your bundle
npm install webpack-bundle-analyzer
npx next build # generates bundle reportNext.js App Router Patterns
Server Components (default): Data fetching, database access, secrets safe
export default async function Page() {
const data = await db.posts.list()
return <PostList posts={data} />
}Client Components (use sparingly): useState, event handlers, browser APIs
'use client'
export function Sidebar() {
const [open, setOpen] = useState(false)
return <button onClick={() => setOpen(!open)}>Toggle</button>
}Boundary: Fetch in Server Component, pass minimal props to Client Component.
Data Fetching in Client
'use client'
import { useQuery } from '@tanstack/react-query'
export function PostList() {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: async () => (await fetch('/api/posts')).json()
})
return <div>{data?.map(p => <Post key={p.id} post={p} />)}</div>
}Red Flags Checklist
- Redux storing server data (sync nightmare)
- Namespace imports preventing tree-shake
next/imagenot used for images- No code splitting at route boundaries
- LCP > 3s or bundle > 250KB (unoptimized)
- Forms without loading/error UI
- API calls in components without React Query
- Prop drilling when composition or URL state works
- Hydration mismatches (server renders X, client renders Y)
- No pagination on large lists (performance cliff)
Refactoring Risk: File Splits and Module Extraction
When recommending that a large file be split into smaller modules (e.g., splitting
a monolithic api.ts into market.ts, portfolio.ts, etc.), you must flag this
as a high-risk structural operation. File splits in frontend codebases are
particularly dangerous because:
- TypeScript/JavaScript barrel files (
index.ts) have subtle re-export semantics —export *does NOT forward default exports - Vite/webpack module resolution caches can serve stale errors after file moves
- Import chains cascade — one broken split file can error 50+ consumers
- Function declarations, interfaces, and type definitions span multiple lines and cannot be split by line ranges
When recommending a file split:
- Flag the risk level explicitly (HIGH or CRITICAL)
- Note that the original file must be preserved until all splits compile
- Recommend splitting in rounds of 3-5 modules with compilation checks between rounds
- Include barrel-file re-export requirements (especially default exports)
- Estimate effort to include verification overhead, not just the code moves
A 3,000-line file that works is better than 14 broken 200-line files. Only recommend splitting when the benefits clearly outweigh the risk, and always include the verification protocol in your recommendation.
Key Principles
- Rendering strategy drives everything else. Choose this first.
- Server state ≠ client state. Use React Query + Server Components for sync.
- Measure Core Web Vitals in production. Lighthouse is a guide; user data is truth.
- Ship less JavaScript. Every byte costs LCP and INP.
- State should live near where it's used. Lift only when necessary.
- URL is state. Filters, search, pagination belong in params, not Redux.
- Progressive enhancement. Forms should work without JavaScript if possible.
- Verify before you delete. Any structural change (file moves, splits, renames) must pass a full build check before the old structure is removed.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

