Tailwind 3 to 4: Migration Checklist and Visual Regression Audit
Upgrade Tailwind CSS v3 to v4 safely: verify PostCSS, theme tokens, dark mode, borders, focus states, source scanning, and visual regressions.
Practical write-ups from the team that builds the templates — architecture decisions, migration notes, and honest framework comparisons.
Upgrade Tailwind CSS v3 to v4 safely: verify PostCSS, theme tokens, dark mode, borders, focus states, source scanning, and visual regressions.
Learn how to disable Tailwind v4 dark mode without breaking UI: remove dark styles for light-only sites or use a class-based manual theme.
Compare Nuxt, WordPress, and headless WordPress for editorial workflow, rendering, SEO, security, maintenance, and developer ownership.
Nuxt and Shopify are not competing frameworks. Compare a themed Shopify store, a Nuxt headless frontend, and a fully owned commerce stack.
Use Tailwind w-fit for intrinsic controls, not page layout. Learn when it works, how it differs from max-w and w-full, and the flexbox traps to avoid.
Size images in Tailwind without stretching or shipping excess bytes: reserve the aspect ratio, fit the crop, select the right variant, then set loading priority.
Nuxt and SolidStart differ most in team fit, rendering model and ecosystem. Use this practical comparison to choose for a content site or a reactive application.
Build a Tailwind CSS v4 search bar with a real React example: icon positioning, accessible labels, keyboard focus, live filtering and empty states.
Learn when to use @utility, @layer components, @theme or plain CSS for custom Tailwind v4 classes, with examples from a real Next.js storefront.
Prompt left React Router at v6. What 8.5.1 actually exports, what the react-router-prompt package costs, and the App Router equivalent.
signInWithOtp sends the link; the click has to land somewhere a cookie can be set. The PKCE callback this storefront ships, and the cross-device failure.
Every public table and function becomes an endpoint the moment it exists. Six tables, six select-only policies, four RPCs with EXECUTE revoked.
A Vue framework and a CSS preprocessor are not alternatives. The real question is whether a preprocessor still earns its build step, answered in 2,801 bytes.
Vue's Nitro server against React's loader and action model. In a working paid backend, 760 lines are framework-free and 226 are adapter. Counted here.
next/head is inert in the App Router. The metadata exports that replace it, and the missing export that un-prerendered 381 OG cards.
role=switch in 30 lines, no dependency. Then the harder part: seven two-state toggles ship here, and not one of them is a switch.
Vue's meta-framework against resumability. We ship neither — but the two decisions behind our Lighthouse 100s are identical in both, and one cost us 1.67s.
The Project URL, the dashboard URL and the connection string look alike. Four files read it here, one accepts the wrong value silently, and an unset one errors every page.
The same ReferenceError means three unrelated things. Measured in Chromium and Node 22, plus the two ways this repo gets __dirname with zero require calls.
ComponentProps<typeof X> in one line, then the harder question: 92 components here use it zero times. The two patterns that remove the need, and the one that doesn't.
What Tailwind actually does for a production site, what it refuses to do, and the measured cost: 41 tokens, 92 components, 15,162 gzipped bytes of CSS.
Bulma 1.0.4 ships 96 files and zero .js, so your Vue code owns every interaction. The six components this storefront had to write for exactly that reason.
Bubble includes the expensive half: auth, data model, workflows. Priced in this repo's own files — 8 migrations, 6 RLS policies, 31 test files — and what code buys back.
Five ways to change pages in the App Router and where each runs. Counted in a real codebase: 26 files use Link, 2 call useRouter, 12 redirect() calls.
Nuxt builds the pages, Bootstrap styles them, so they are not rivals. The styling options under Nuxt, with one measured build: 1 CSS file, 14.7 KiB gzipped.
Both can be fast and both can look good. The decision is whether your pages are derived from your own data — and whether the source code leaves with you.
Props are React; the serialization boundary is Next.js. The four shapes a 92-component codebase uses, including a Server Action and three server-rendered slots.
Copy searchParams into a mutable URLSearchParams, then decide push or replace. Three real writes: a dashboard tab, a validated ?next= redirect, a checkout hand-off.
Next.js is React plus a router, build and server rendering. Measured on this repo: 884 prerendered pages, and a client FAQ whose text is still in the HTML.
"use client" still prerenders on the server. A probe build shows what ssr: false emits instead, and how useOwnedProducts keeps per-viewer UI off the static HTML.
The --typescript flag is a no-op in Next 16. The 24-option tsconfig, the gitignored next-env.d.ts that turns out not to matter here, and where the type check really runs.
The sections are the easy part. Three constraints a SaaS page skips: no clinical detail in the form, no person in the analytics, and a CSP that makes every tracker a commit.
There is no per-project invite below the Team plan. The four roles, the 24-hour invite, and why 271 lines of migrations beat a dashboard seat for most collaborators.
Next.js routes and renders; Tailwind generates CSS. Measured on this repo: 876 static pages, one 15 KB gzipped stylesheet, no Tailwind config file.
HubSpot is a CRM with a CMS attached. What a Next.js storefront had to write instead: a Server Action, a CSP that blocks form embeds, and attribution with no person in it.
@ts-nocheck must be line 1, tsconfig exclude does not stop checking, skipLibCheck saved 0.2s, and ignoreBuildErrors deleted the only type gate this CI had.
Ghost is a publishing platform; Next.js is a framework. A comparison from a 361-post MDX blog beside a 111-product storefront, plus the link-graph defect only code could fix.
Where a hosted builder stops: logins, gated downloads, pages derived from your own catalog. With the Lighthouse table this Next.js storefront measured.
Maintenance mode since Feb 2024, archived Oct 2025. The 158-line Server Action file that replaced it here, and the 68 KiB it kept off four pages.
request.nextUrl.searchParams is one line; the next twenty decide whether the endpoint is safe. Walked through one entitlement-gated route handler.
Every declaration lands in one of four places. Measured on a 92-component port: 41 tokens, 96 arbitrary values, and 12 hex colors that duplicated a token.
The connection runs SQL as `postgres`, which carries rolbypassrls. Measured against six RLS-enabled tables, plus the migration drift it surfaced immediately.
Both take the payment. The difference is the delivery path you own — 1,547 lines here, the webhook guards that keep it safe, and the bug that grants nothing.
What ejecting costs, measured: 846 prerendered routes on the line, plus the five jobs people reach for a custom server for and the file that owns each.
Both the forwardRef and the ref-as-prop shape typecheck on React 19.2.4. Eight peer ranges measured today, and the cleanup copy-in leaves you owning.
The proxy keeps the session fresh; layouts and route handlers decide access. A 42-line proxy.ts and its matcher, run against 21 paths: 11 hit it, 10 skip.
Tokens into @theme, the dark variant, then the components. Compiled selectors measured in a v4 repo: why :is() and :where() change your cascade.
Three families of alternative, and the measured cost of dropping the CMS entirely: 352 MDX posts into 852 static pages in ~12s, plus what you give up.
Tailwind v4 classes set CSS properties and register --tw-* variables with @property. Read from a real 84 KB build: 58 rules, how translate and shadow compose.
React 19 removed propTypes for function components. Zero declarations across 92 component files here, one copy still in node_modules, and the three patterns that took over.
ISR refreshes static pages without a deploy. This build prerenders 846 pages and exports revalidate zero times, because compiled-in data has no staleness to correct.
Gatsby resolves data at build time; Qwik never hydrates. Measured against a build that chose neither: 846 static pages, no GraphQL, 24 of 92 components on the client.
A React email builder is three different products: an embedded editor, a component library, or a plain string. The question that decides which one you need.
Gatsby generates pages, Bootstrap styles them — they are not alternatives. Which job you are shopping for, priced against one build: 840 static pages, 84 KB of CSS.
A custom checkout is mostly data and button states, not a payment form. The 3 URL fields we set, 5 button states, and the variant-id mix-up that charges buyers and grants nothing.
An online store checkout is a form plus a webhook pipeline. The 8 checks our 287-line handler runs in order, and the six cases that would lose a paid order if skipped.
One 19-line Server Component emits seven schema types across 472 pages. The escape most tutorials omit, and two blocks we ship knowing they earn nothing.
A canonical is one line of metadata; deciding what collapses into it is the work. The facet URLs we never created, and two noindex pages that keep theirs.
A Next.js storefront is a static catalogue, a hosted checkout, a signed webhook and a fulfilment check. The code, the Lighthouse numbers, and three defects we shipped.
Firebase replaces your backend; Prisma replaces your queries. Audited against 6 RLS policies, 6 SECURITY DEFINER functions and one TOCTOU bug that shipped.
27 pages, 4 route handlers, 13 metadata files in app/ — and not one component. The real tree behind 111 products and 337 posts, plus the rule that keeps it honest.
One line turned a 112 KiB static page into a server render, 820 prerendered pages to 819. Measured in a real build, plus the 8 routes that go dynamic with no config.
A controlled file input with preview and validation, the object-URL leak it hides, and the build-time resize that cut 110 covers from 15,102 KiB to 3,570 KiB.
Firebase bundles auth, storage and functions; PlanetScale is MySQL alone. Audited against 271 lines of production SQL, 7 foreign keys and 8 CHECK constraints.
PocketBase is one Go binary with embedded SQLite; Firebase is a managed bundle. The axis nobody compares: what each costs when your front end holds no state.
Measured on Next 16.2.9: edits reach the page in 0.35–1.5 s. The five causes of broken React hot reload, including the CSP one tutorials skip.
Map every Next.js 13 auth step to Next 16: middleware is now proxy, cookies() is async, and getClaims() replaces unverified session reads.
Measured on Next 16.2.9: the three .env files dev loads, which one wins, the build-time freeze, and the 500 every request throws when one is missing.
Hosted or self-hosted is the real axis. The CSP cost of each, measured against a live policy, plus why script weight is the wrong thing to compare.
Every numeric size is calc(var(--spacing) * n). Measured across 139 files: which families get used, the fractional steps, and the sizes that aren't classes.
Rules in a DSL or logic in functions — judged on a shipped schema, including the check-then-act race that proved declarative rules can't hold an invariant.
Tailwind v4 wraps hover: in a hover media query. Measured in a real browser, plus the recipe, the hover-only overlay bug and a troubleshooting table.
Error 42501 fires before RLS runs. The four gates a Supabase query passes, the grants that fix the schema one, and the two this schema trips on purpose.
Directus layers over your own SQL; Hygraph holds content in a federated graph. The axis that decides it, and why this blog's 326 posts run on neither.
Only 24 of 92 components here run in the browser. The ratio that decides the choice, plus two defects showing what a framework does and does not buy.
What each region of a cart page has to prove, a two-stage commit lifted from a real redemption picker, and the mobile reorder that needs no second button.
A 70-line cart with data attributes, one delegated click handler and localStorage, then the production version of the same pattern from a real storefront.
Where searchParams is available in the App Router, where it is not, and the one-line guard that stops ?next= becoming an open redirect.
React 19 absorbed the submission lifecycle, not the validation engine. The API-by-API split, five forms with no form library, and the a11y trap.
A browser runner and a Node runner, scored against seven defects this codebase actually shipped. Measured: 391 tests in 4.40s, and three bugs neither caught.
Where focus-visible: beats focus: in a real codebase, the ring recipe behind every CTA, and the invisible-overlay bug no variant can fix.
A React navbar from a real 200-line header: aria-expanded toggle, scroll lock, Escape to close, and why pointer-events-none left links tabbable.
The Pages Router function has no App Router export. Here is the mapping, using the dashboard, auth layout and notFound pages of a real Next.js 16 app.
Bundle size is two numbers, not one. Measured here: a 195.2 KiB gzipped floor identical on every page, and the 68 KiB auth stack that used to sit inside it.
React cannot send email, so the only real decision is who does. Three routes compared, plus the 40-line action this site runs — honeypot, reply_to and error hygiene.
They overlap on one thing and solve different problems. What each catches, what browser binaries cost every CI install, and four defects here that neither would have found.
Utilities, CSS-in-JS, BEM or native @scope: what each gives up. This repo has no module files and ships one 83 KB stylesheet (14.8 KB gzipped) for every page.
Both are API-first headless CMSs. The real split is whether editors arrange pages themselves -- and what that costs your component layer.
Both give you end-to-end types. Neither stops a check-then-act race -- the CWE-367 defect this codebase shipped, and the SQL that fixed it.
Edge read latency only helps if reads block a request. Here they do not -- so the 68 KiB auth bundle was the real latency, and a regex fixed it.
Suppress a TypeScript error on one line the safe way. Measured TS2578, TS2790 and ban-ts-comment output, the multi-line trap, and a cast that avoids the directive.
getValues reads without subscribing — proven from the 7.89.0 source. Why that breaks a disabled button, when to use watch, and the useState gate this repo used instead.
The same card built three ways and weighed. Why Bulma ships the whole framework, where daisyUI's 6.4 KB actually goes, and the token cost both bring along.
Bulma is written in Sass, so the real decision is precompiled or compiled. Measured: 65.2 KB down to 25.3 KB — with an 18.2 KB floor the theme layer makes mandatory.
Use Next.js as a static site generator two ways: output export or the default build. Measured here: 754 prerendered pages in 8.8s, with 8 routes that need a server.
How to schedule with pg_cron — and the three nightly jobs an 8-migration commerce schema replaced with a rolling window, a foreign key and a pure function.
The Server Action, the shared PKCE callback and the three dashboard surfaces that actually break Google sign-in — including the trailing slash that cost us a redirect.
What supabase gen types typescript produces, and why one shipped commerce backend runs six tables and 271 lines of SQL with zero generated types.
Angular renders components, Tailwind generates CSS. Measured here: one 42-token Tailwind file styles 111 products across four frameworks in 14.8 KB of gzipped CSS.
Eight Shopify Markets settings audited against a digital storefront: five are empty, VAT belongs to the MoR, and one currency fallback was printing a dollar sign that lied.
What the managed platform owns and what stays yours: 271 lines of SQL and 102 of client code move; the advisor linter, the pooler and six dashboard settings do not.
Shopify's four Online Store 2.0 ideas mapped to a shipped Next.js codebase: 33 organisms, typed metafield-style fields, one 14.8 KB stylesheet, and the editor you cannot port.
Monitoring, stating what a user has, offering actions, and auditing them. Which jobs a real 505-line dashboard does, and the two defects it took to get right.
Checkout, order records, apps, the admin: each Shopify advantage measured against the 1,545 lines of TypeScript and 271 of SQL it took to replicate a subset.
The six stages mapped to the files that implement them in a shipped store, plus the four failure modes that all cluster at one hop: order processing.
Next.js routes, renders, bundles, runs server code and emits metadata. Each job checked against this storefront's build output: 425 HTML files, 8 dynamic routes.
The folder contract is unchanged, but six APIs drifted: async params, searchParams and cookies(), the proxy rename, and two changes that never warn.
Six image findings worth 1.67s of load delay, three a11y defects that survived a Lighthouse 100, and the third path both options' marketing pages skip.
A PAT authenticates your account to the Management API, not your app to its database. The four credentials, which plane each sits on, and why this app ships three.
What belongs in next.config.ts and what doesn't, from a real storefront's 121-line config — 11 CSP directives, and the MDX plugin Turbopack silently drops.
object-cover 7×, object-contain 0×. The two patterns that establish the box, why gallery slides need object-top, and the wrapper mistake that breaks it.
Centering, drawers and hover swaps across 8 components — plus the containing-block defect a no-op translate-x-0 shipped, and why a computed value can't be a class.
supabase-js, @supabase/ssr and the admin client, read from a real Next.js 16 storefront: 18 queries, getUser vs getClaims, and the lazy import that removed 68 KiB.
The App Router routes special files, not folders — so the placement question is about one import path and one place to look, not about avoiding accidental routes.
Five caching layers, and the render layer is rarely the one that pays. Nine lines of module-scope promise removed a dozen round trips per page load.
One axis decides it. What content-in-git actually costs, measured at 282 posts — including the bundler defect that only exists on that side of the line.
Seven requirements written as pass/fail acceptance criteria, each with the number this storefront actually measured — and the defects that changed the list.
Error 57014 is almost never the timeout's fault. On Supabase it is usually a policy calling auth.uid() per row against an unindexed column — with the six-policy fix.
Both ship visual editing, so the feature grid hides the real trade. Git-backed means you build the content graph yourself — measured, with the defect that proved it.
Strapi's GUI writes your schema; Payload's schema is TypeScript you review. That one difference decides the rest — measured against 391 tests and no CMS at all.
Both put a server and a database between your pages and your words. Measured against a build that serves 702 static pages in 38s with no content server at all.
A dashboard tab switcher with three of six required ARIA parts, the fix that shipped, and what this site's 9 admin templates are actually built to solve.
Marketplace listing, hosted platform, or owned storefront: the four real files (75 to 373 lines) this site's entitlement engine and download route are built from.
120+ components vs. 92 hand-rolled ones, and the two real ARIA defects this codebase shipped with that a component library would have caught on day one.
Zero styling runtime vs. a render-time serializer, measured against 92 hand-built components, a 24/92 client-boundary ratio, and a CSP line Emotion wouldn't need.
The axis is where content lives. Plus the case where neither wins — 269 MDX articles and 470k words here run on a typed registry and a test, no CMS.
Three methods, one of which trusts an unverified cookie. Counted across this codebase: 14 getUser() calls, 3 getClaims(), and zero getSession().
v4 has no tailwind.config.js, so every tool needs pointing at your CSS instead. Six causes, the Prettier pointer this repo sets, and the one that isn't tooling.
The utility is width:100% plus a max-width per breakpoint — no centring, no padding. Read from v4.3.1's source, next to the 43 lines of CSS that replaced it.
Supabase has no native Zapier integration. Here's the signed webhook route and one atomic Postgres RPC this storefront uses instead, and why a Zap chain can't match it.
Explicit named props, a full data-object spread, and forwarding the rest — 7 real spread call sites and Button's own default-parameter signature, from a 92-component codebase.
A fresh build generates 678 prerendered pages and 8 dynamic routes. None of the 8 mix a static shell with a dynamic hole — which is exactly the shape PPR needs to pay off.
A real 19-class hero attribute, the 'keep every class' policy that keeps it that way, and the one compiled stylesheet — 81,866 bytes for 111 products — that pays for it.
Sanity gives you a schema in TypeScript and nothing to patch. Headless WordPress gives you an editor your team knows, and a PHP stack you still operate.
Payload installs into your Next.js app, so content reads skip HTTP entirely. Sanity hands the 3am pager to someone else. Measured against a build with no CMS at all.
Squarespace is a finished product you fill in; Webflow is a design tool that hosts. The axis nobody checks during the trial is which way out you are choosing.
Most guides show a string-keyed registry. This codebase's 92 components use three other shapes instead — a variant prop, selection state, and data that carries its own JSX.
Link prefetches on scroll into view, before anyone clicks. This storefront keeps one href out of Link entirely because a prefetch there would burn a buyer's download quota.
Three call sites derive the same narrow WishlistEntry from a full catalog product, and none of them spread it. Here's why, plus the factory-function shape that spreading can't replace.
This repo has zero JS config files. All 42 design tokens live in a CSS @theme block, and the four-line PostCSS file is the entire build pipeline.
Tailwind v4's package declares no `bin`, so npm links nothing. Measured in this repo: 26 entries in node_modules/.bin, none of them tailwind.
Eight migrations, seven dashboard settings a migration can't carry, and why a missing Supabase env var takes down 111 product pages instead of one.
The page converts; the download has to not leak. A 60-second signed URL, a private bucket, and the honeypot most ebook-page advice leaves out.
Five client factories map onto two: createBrowserClient and createServerClient. The cookie adapter and getSession() are what find-and-replace misses.
This storefront ships 92 components; only 26 carry "use client". What actually earns the directive, and the leaf-component pattern that keeps the rest server-only.
This storefront's auth stack is 812 lines of Server Actions, not the Supabase UI library. What the library would replace, and what it wouldn't touch at all.
This site's entire analytics surface is five typed events and one CSP entry. What Matomo's self-hosted depth would actually cost to run instead.
614 lines of LemonSqueezy integration compute zero sales tax, because LemonSqueezy is the merchant of record. What a PayPal-only checkout puts back on you.
This storefront sends mail from three files, all of them one fetch call. What SES would change, what it would improve, and what neither vendor fixes.
Eight migrations, six RLS policies and 357 tests in 3.3 seconds — with no Docker and no local stack. What that buys, and exactly where it stops working.
Both are merchants of record, so the tax question is settled. An inventory of the vendor-specific code in this storefront, and what a migration would cost.
Postgres with RLS, auth and storage versus edge SQLite. An inventory of what a move off this storefront's Supabase stack would actually have to rebuild.
An audit of this storefront's eight commerce migrations — three create, five change or fix — plus the three habits that make one safe to re-run.
This layout loads three scripts and only one is a <Script>. A census of which mechanism each needs, and what every script costs your CSP.
Four places an upload can live, compared — and the release pipeline this storefront uses instead, with the scanners that fail closed before it.
This codebase has one interface with two implementations — the textbook case for a class — and reaches for a class exactly once, in a test file.
This repo generates real route types on every build. What .next/types actually contains, and why this project still hand-writes its own params types instead of using the new helpers.
139 typed files, 92 components, zero React.FC and zero any. The props pattern, the exhaustive union and the Record lookup that carry a production React app.
JSX compiles to function calls, not HTML. Where the two diverge, why a lowercase SVG tag survives dev and fails next build, and the key rule behind phantom state bugs.
Both are cookieless with a one-line install. The difference is what happens when you leave your host, measured against this site's live CSP, event wrapper and Lighthouse run.
Supabase bundles Postgres, auth, storage and atomic RPCs behind one SDK. Mapped against this app's real files, here's what RDS, Cognito, S3 and Lambda would cost to assemble instead.
Chakra needs a client provider; daisyUI is a Tailwind plugin with zero JS. Measured against this codebase's 92 components and its own design tokens, only one can join the tree as-is.
CSS Modules scopes class names; Sass preprocesses them — different jobs, often combined. This repo's entire hand-written stylesheet has two custom classes, and one is dead code.
reactive() cannot hold a primitive, and 30 of this codebase's 33 state values are primitives. Of the 3 objects left, 2 do the thing reactive() breaks on.
npm ci on this storefront installs 633 packages and 742 MB. React is 7.5 MB of it — what the other 99% is, and which of the three install paths to pick.
Supabase's built-in email provider caps auth emails project-wide. This codebase relies on that limit by design (R-13) instead of building a bespoke one — and builds one anyway, elsewhere.
This codebase's first rule of ARIA in practice: 13 role attributes across 9 files, each filling a gap HTML has no native element for — plus the live-region defect every form here used to ship.
A Server Component prop, a client hook, and a Route Handler's own request object all read the same URL differently. This codebase's two real reads are both the third kind, for security reasons.
SWC compiles fast and never type-checks; tsc does the opposite. This codebase's own tsconfig and a Turbopack defect it actually shipped show exactly where that split shows up in practice.
This storefront has exactly one real dropdown, and its header explicitly rejected a second in a code comment. What DownloadMenu gets right that a div toggle usually skips.
44 absolute, 37 relative, 14 fixed instances audited — plus the scoped relative wrapper that stops a stretched card link from swallowing a neighboring button.
Three components spin a Loader2 icon; three more swap button text instead and skip it entirely. The rule that decides between them, audited across this codebase's seven loading states.
A census of every p-*, px-* and py-* in this codebase — 89 distinct values — plus why the site's page gutter lives in globals.css instead.
Zero class components here. The split that replaced function-vs-class is 68 Server Components shipping no JS against 24 client ones that need state.
Why this codebase writes aspect-[16/9] instead, and how the card's ratio has to agree with the img's width, height and the generated file's width.
This codebase reads process.env 37 times, including in browser code, and never hits the error. What bundlers really do with it — and the prefix trap.
This codebase's h-* utilities are mostly icon sizing. The one true viewport lock is h-[100dvh], not h-screen — because a static 100vh lies on mobile.
114 aria-* attributes across 36 files carry every state hook this codebase needs. grep for data- in src/components and you get nothing back.
No @plugin line in globals.css, no @tailwindcss/forms or typography. A hand-rolled container, marquee keyframes, and no-scrollbar cover what plugins usually do.
Every icon on this site is JSX assigned to a data field, not a name looked up inside a component. 35 instances across 5 data files, one helper function pattern.
Not alternatives. Vercel builds and serves 584 pages, Supabase holds the schema and the policies, and one 42-line proxy can take the whole site down.
Open the Components tab on an App Router page and the tree looks empty. 68 of 92 components ran at build time and never mount in a browser.
One icon library, twelve imports, 55 inline SVGs — and the split falls exactly on the client boundary rather than on taste.
296 flex-utility call sites across 68 files use 6 of Tailwind's 12 flex classes — the other six, all per-child grow/shrink tuning, are never needed.
Next.js names two component types. This codebase runs five layers across 92 files, and 79% of its client components live in just one of them.
Zero of shadcn, Radix, MUI, Chakra or daisyUI in this package.json. 92 components run on Tailwind v4 tokens and one enforced import rule instead.
This storefront sells 113 products with zero cart code: one Server Action resolves a fixed-tier checkout, and a slot-based entitlement engine replaces the order.
Firebase bills $0.026/GB stored and $0.15/GB transferred. This storefront's 156.5 MB build uses 1.6% of the free storage tier — transfer is the line that bills.
A census of a real TypeScript codebase: 4 forEach calls, 149 .map() and 137 for...of. What the four have in common, and the async trap that explains the ratio.
Vite builds, Vue renders, and most projects use both. The comparison that resolves is Vite vs. Vue CLI — plus which layer to blame when a build breaks.
Both answer routing, data loading and form posts the same way. The divergence is what reaches the browser — and how much of a page you can avoid shipping at all.
Qwik ships near-zero JS through resumability; this site's RSC split already does that for 9 of 10 home-page sections. Where the real cost still lands: one accordion.
Six ingredients build an accessible nav bar — and this site's own mobile drawer shipped the pointer-events-none bug that leaves closed links keyboard-reachable.
193 font-weight utility calls across this codebase, spanning only 4 of Tailwind's 9 named steps — traced to two atoms that set the hierarchy once.
EmailJS ships its service ID, template ID and public key in your bundle by design. This storefront's contact form ships none of that — the two files that decide it.
In Postgres “schema” means both the structure and a namespace. Here is a real commerce schema — 6 tables, 8 migrations, and the four times it had to change.
Measured on this storefront: 334 HTML files from 27 route files, and 68 of 92 components that never reach the browser. Plus where a CRA-shaped app still wins.
Tailwind CSS is MIT and free. The confusion is Tailwind Plus and a template's own EULA — three different licenses hiding under one name.
CRM means customer relationship management — audited against 9 of our 111 templates, from one dedicated CRM to a landing page that only markets one.
A 538-page site's whole grid vocabulary is three utilities — plus the display:contents trick that reorders a product page on mobile without duplicating state.
A measured head-to-head on 538 pages, plus the config trap: Turbopack serializes options to Rust, so an imported MDX plugin now fails the build outright.
Read in one file, guarded by server-only, used at six audited call sites — and why dropping one .eq() returns every user's rows instead of none.
92 components, 13 runtime dependencies, zero UI libraries — what hand-rolling actually cost this codebase, and the seven components shadcn would have handed over.
This storefront's whole backend is 2 Route Handlers, 8 Server Actions and a 42-line Edge proxy — no Cloud Functions, no separate BaaS deploy.
Grep-verified: zero client components query a table directly, so this schema's six row-level-security policies defend a path the browser never uses.
8 migrations, 6 RLS policies and one site-wide outage risk — why a Vercel-hosted storefront still runs Supabase for its database and identity.
Measured on this repo: 31 files, 331 tests, 2.48s — and the `environment 3ms` line that explains most of the Vitest-versus-Jest gap.
A production dropdown, defect by defect: why one `open` boolean breaks the click, and why the 8px gap has to be padding rather than margin.
SolidStart is the real comparison. 24 of 92 component files here ship client JS — the number that decides fine-grained reactivity against RSC.
5 real Server Actions, an admin-client rule, and the one line that stops a deleted user's still-valid JWT from staying logged in.
A real tab-order bug this codebase's own comments document, and why `hidden` couldn't have fixed it — `display: none` can't be part of a CSS transition.
27 routes, 2 layout.tsx files, and 11 places that render the same header and footer independently — the one nested layout isn't for shared chrome.
12 files declare a group, 3 need a named one — because their outer group already answers to a hover the inner one has to ignore.
Three Supabase clients, not one — and why every authorization decision here uses getClaims(), never getSession(). Plus the auth stack that reached every page.
Counted every display utility in this codebase: inline-flex took 53, inline-block kept 3 — and all three are the same case, a vertical margin on inline text.
Both — and the split is countable: 8 Server Action modules, 4 Route Handlers, one HMAC verified over raw bytes, and no database, ORM or scheduler anywhere.
Six tables, six SELECT-only policies, zero INSERT policies anywhere — the exact error this schema guarantees, and where writes actually happen instead.
Compiled this project's own stylesheet to check — a plain leading-[1.2] would already win the cascade over text-3xl's paired line-height, no ! required.
No ORM, no Artisan, no PHP anywhere — 271 lines of SQL and 897 lines of Server Actions doing what Laravel's migrations and controllers do instead.
Django's admin panel is free and automatic. This storefront's buyer dashboard is three files, 556 lines, purpose-built instead of generated from a schema.
1,160 image files, zero imported ones. The import-vs-string-path choice is really a choice about who verifies the path — and what you have to build once the bundler stops.
npm carries sixteen dist-tags for `next` and `latest` is only one of them. Which version to be on, measured from a repo that pins the framework and ships it to other people.
24 useState, 13 useEffect, 0 useContext across 22 files — a real hook census, and the module-scope store pattern this codebase uses instead of Context.
One real <textarea> across 111 products, a honeypot beside it, and a silent mismatch between the field's missing maxlength and the server's 5,000-char cap.
Neither is a dependency here — but the Vite version vitest pulls in has already dropped esbuild for Rolldown, proven straight from the lockfile.
Accessibility 100 on all 8 pages, and two real defects no scanner flagged. Four defects sorted by which tool found them — and why two were structurally invisible.
Answered with three measurements instead of taste: 133 hand-written lines, 14 KB gzipped across 488 pages, and the AA contrast bug the token scale made easy to ship.
Most comparisons assume you pick a bundler. On a framework you inherit one — and the two config lines this codebase writes anyway are where the real difference shows.
9 admin templates, 59 dashboard views, audited from the catalog — what a coded data dashboard actually contains versus a BI tool.
Two Route Handlers, zero Dockerfiles, zero nginx configs — what this storefront's own deploy setup says about Vercel vs a DigitalOcean Droplet.
Ten organisms, one deliberately unmounted, and a real LCP image bug — what actually builds a landing page, audited from the source file.
55 inline styles across 21 files in a Tailwind-only codebase — audited by hand, each one tied to a runtime value or a non-CSS renderer.
React ships no router, no data layer, no build step and no server. Settled with inventory: 139 framework editions across 111 products, and the 9 built twice.
A hosted design you edit in a builder, and a source repo you own, are both called templates. What is actually inside one, from a catalog of 111.
Four things, precisely: auth, row-level authorization, private storage, atomic writes -- including the rate-limit race this app actually hit and fixed.
Gatsby adds a GraphQL data layer on top of React. This catalog imports typed data directly instead, with zero GraphQL and zero content plugins.
139 components, 881 classNames, one 133-line stylesheet: the exact 40 lines of hand-written CSS a Tailwind codebase still needs, and why.
Buying a template never transfers copyright in its code. What you actually own, plus the hardcoded footer year this codebase shipped unfixed for a month.
no-html-link-for-pages reads literal hrefs on raw anchors, so it never saw this site's button atom. Plus the internal URL that must stay an anchor: /api/download.
The component called Button renders a link all 22 times it is used, while 44 real button elements sit elsewhere. The full class list, and the disabled state it was missing.
An audit of four overlay surfaces: the wishlist panel that let Tab walk out of a modal dialog, the off-screen drawer pointer-events-none never hid, and the iframe case.
v4 deleted the init command, the JS config and the content globs. What a real React setup is instead: one import, one PostCSS plugin, 879 class attributes, 133 lines of CSS.
The dialect differences port. The 7 foreign keys into auth.users and 6 policies built on auth.uid() don't — inventoried line by line from this storefront's 8 migrations.
Studio's SQL Editor is built for one-off queries. Every schema change here shipped instead as one of 8 reviewed migration files — what each tool is actually for.
Most tutorials assume a step-index useState pattern by default. This codebase's one real example is two states confirming an irreversible choice, not more fields.
Not React versus Vue — the real split is where each framework lets you draw the server/client boundary. Next.js draws it through the import graph; Nuxt's default is universal.
Squeeze-page advice says strip the page to one form. Only 14 of 66 landing templates in this catalog do — audited against this storefront's own single, sitewide capture point.
Astro assumes a page is static until a component opts in; Remix assumes a route is a server request until it opts out. This build's own 452-static/8-dynamic split shows why that matters.
Webflow's ecommerce plans cap items and charge a transaction fee on top. This storefront's entire commerce stack — entitlements, checkout, webhook — is 843 lines with no cap at all.
Courses teach the Next.js API in isolation. This walkthrough traces one real request through five actual files in a shipped storefront, linking to deeper audits at every stop.
Agency guides bracket redesign cost by page count. The real driver is content versus architecture — audited from this storefront's own 111-page copy rewrite: one commit, 32 lines of code.
Amplify, Lambda via SST, ECS Fargate, or EC2 — AWS has no framework-aware default. This build's own 428 static and 8 Node-runtime dynamic routes decide which compute layer actually fits.
DI-injected signals versus a build-time compiler. Both assume a component needs a reactive primitive — 7 of this codebase's 24 Client Components prove a third of the time, it doesn't.
Tailwind's font-family utilities point at a CSS variable next/font has to define. One Outfit import, two self-hosted .woff2 files, 46,988 bytes, zero requests to Google's font servers.
A Tailwind class is a compiler-generated CSS rule that only exists because a literal string appeared in your source. Audited from 658 className attributes and 91 components.
GitHub Pages has no compute layer at all. The five alternatives worth naming, measured against this build's 420 static routes and 8 dynamic ones.
next-seo is a Pages Router package the native Metadata API replaced in Next 13. Zero dependency here, 28 metadata sources across 27 pages — the full migration mapping.
The return URL and the webhook are two independent HTTP calls in parallel. This storefront resolves the race by rendering the receipt only from Postgres rows the webhook wrote.
React is a UI library, Bootstrap a CSS framework — the real fork is Tailwind vs. Bootstrap, measured against this site's single 74 KB stylesheet and zero jQuery dependency.
No Sentry, no instrumentation.ts — 41 structured console.error calls, an ALERT-marker convention, and the error-hygiene test that keeps server detail out of the browser.
8 migrations, 6 RLS policies built on auth.uid(), and a write path that deliberately isn't rule-gated like either platform's default posture.
The STOREFRONT_COPY_REVISED fix for a sitemap that told Google 111 pages changed on a date nothing did, plus the noindex-vs-disallow split this codebase actually uses.
A census of one production app/ tree: which of the nine reserved filenames actually earn a file, and the 40 links, 13 redirects and 2 routers that move between them.
One error.tsx covering 27 page files, no global-error.tsx, and a digest the boundary declared but discarded — the audit, and both fixes that shipped with this post.
Four ways to print money in one codebase, and the fallback that stamped a dollar sign on any currency Intl could not parse. The audit, the fix, and the locale rule.
No key field anywhere in this codebase's entitlement types — ownership is a database row scoped to a session, checked fresh on every download.
Server Actions get automatic Origin-header CSRF checks. This codebase's 4 Route Handlers don't — a census of what actually protects each one instead.
A real production nav, from a 4-item desktop bar to a slide-out drawer — the aria-expanded toggle, the z-index stacking, and what a div-only navbar loses.
Zero details elements across 91 components, and the CSS grid-template-rows trick FaqItem uses instead — plus when the native element is the right call.
Episodes belong in a typed registry, the feed is the actual product, and the player embed renders blank unless your Content-Security-Policy names its host.
Not a gallery — a section-by-section census of 17 SaaS landing templates. Pricing, testimonials and integrations each appear in 10 of 17; the median product is 11 pages.
The storefront half is solved and cheap. The grocery half — delivery slots, substitutions, live stock, weight pricing — ships in zero of the 35 shop templates here.
414 prerendered pages any CDN serves the same way, 8 dynamic routes, and exactly 2 that pin the Node runtime for node:crypto. Those two lines are the whole decision.
Real per-step timing from a production run — 100 seconds total, cheapest checks first, no separate typecheck step because next build already does one.
13 runtime dependencies, zero AWS SDK packages, and a build that splits 392 static pages from 8 dynamic routes with no deployment config authored for either.
Sass's four features, checked one at a time against this codebase's real @theme block, group-hover usage, and the single [&_selector] Tailwind still reaches for.
This storefront's home page prerenders to 436 KB, and 52.1% of that is a serialized copy of the render, not the page. What actually reaches a crawler before any JavaScript runs.
The build is the smallest number in the project. Priced from a real page: ten components and 1,256 words, then four attachments and the two assets no licence includes.
41 tokens across three of Tailwind v4's 19 namespaces, and 161 utilities that spell a value literally instead. 22 of those literals were already a token in the same stylesheet.
Two routes, two Server Actions, and one redirect that exists because a Server Component render cannot write a cookie. Plus the enumeration answer that must not vary.
Eight Server Actions in this codebase split cleanly on one rule: revalidate what stays, redirect past what doesn't. The one action that breaks the pattern on purpose.
Client-side checks are UX. The server-side function that reads three named fields and ignores everything else — proven by a test that smuggles in a fake user_id — is the actual gate.
Route groups hide a folder from the URL. Every one of this app's 24 top-level folders needs to be in the URL — which is the exact case they're not for.
Two Route Handlers, both stateless, neither overriding the default timeout — versus a release script that clones, installs, and boots for minutes. That split is the real decision axis.
Suspense buys streaming, and a page rendered at build time has nothing left to stream. Zero boundaries and zero loading.tsx here, plus the useSearchParams de-opt everyone reports wrong.
Six Server Actions, a 68 KiB auth SDK kept out of every initial bundle, and one line that decides the whole security posture: getClaims(), never getSession().
Point Prisma at a Supabase database and your policies stop working — in one of two directions, neither the one you wrote. Our own six policies, audited against that.
Every chart library is a client component. What that actually costs, measured here: 42 KB gzipped across twelve routes, from a five-line constant nobody suspected.
Vue's Composition API needs ref/computed for every stateful value, server or client. This storefront's 13 dependencies carry zero state library — here's why.
This codebase paginates nothing — /blog still renders 106 posts on one page, 3.5x past its own stated 30-post threshold. The searchParams pattern for when it's real.
31 test files, zero jsdom, zero @testing-library — every assertion is a data invariant or a security boundary. What that catches, and what it can't.
Remix runs on React — they aren't competitors. The real comparison is Next.js vs. React Router v7, and it comes down to which way the static default points.
Zero toast libraries and five aria-live regions here — all five mounted with their first message, so none of them ever announced. The audit, and the atom that fixed it.
344 prerendered routes, zero trailing slashes, and no trailingSlash config at all. The bug that did reach production came from one env var, and the one-line fix it now has.
Hire for direction, not for pictures of a decision you already made. Six real defects found on a site scoring accessibility 100 — none of them visible in any comp.
Published ranges span $5 to $5,000 a month because they average across architectures. The four things that actually meter, itemised from a 551-package, 344-route build.
Convex's transaction model would have prevented a real TOCTOU race this storefront hit in its Postgres download limiter. Why that's true, and why the fix stayed on Supabase anyway.
This storefront's real waitlist form: the Resend Audience API's deprecated field a copy-pasted snippet would miss, and firing one analytics event per successful subscribe, not per render.
Intercepting routes mask navigation to a route in your own app. This storefront's live-preview modal shows a cross-origin iframe instead — a real reason the pattern never fit here.
GitHub Pages has no compute layer at all — not slower, absent. A fresh build of this storefront counts exactly which routes that categorically rules out, and why it isn't a rendering-mode question.
max-w-* reads the --container-* scale in v4, and max-w-md is 448px, not 768px. A census of 62 usages here, plus the viewport band where our layout narrows as it widens.
332 prerendered routes from 27 page files. Why a per-page quote is unplannable, and the three line items estimates leave out: verification, the post-launch rework pass, content.
Angular needs a DI container before a component can fetch anything; a Next.js Server Component just calls a function. Measured from this storefront's 334-page static build.
This codebase has zero React.lazy() calls. What it actually lazy-loads — an explicit per-post import map and a conditional SDK import — and why that distinction matters.
No calendar UI here, but this codebase already fixed the timezone bug that breaks most of them, twice. The UTC-safe date pattern, applied to a month grid.
A hosted storefront does six jobs. Another one changes the fee; owning your storefront takes them back as code — 1,607 lines here, file by file.
Our whole catalogue page is 485 KB of HTML that ships as 34.7 KB gzipped, with all 111 links crawlable. The measurement first, then the implementation.
Four ways to style React, and the constraint that ranks them: CSS-in-JS needs a client boundary. Plus every inline style in this codebase, categorised.
Appwrite grants roles; Supabase evaluates a SQL predicate per row. An audit of what our own RLS policies would have to become as a Function instead.
Bundle size is the wrong axis for a meta-framework choice. The real divide is routing defaults and whether a mutation can live anywhere or only on the route that owns it.
Six RLS policies, all built on auth.uid() — a function Postgres does not have. An audit of exactly which of our own code depends on the platform rather than the database.
ANALYZE=true writes nothing in Next 16, and the route table no longer prints First Load JS. The replacement, where it hides its output, and the 42 KB one constant was costing twelve routes.
318 pages and zero rewrites, with the compiled manifest to prove it. What each phase beats, why our seven hub pages stayed files, and the 308 redirect nobody configured.
React Router's client-side wrapper isn't the only pattern. This codebase's redirect guard runs on the server, plus the open-redirect check most tutorials skip on the ?next= param.
The only <table> in this codebase, read cell by cell: what it gets right, the missing caption and text-free checkmarks it shipped with, and the fix.
Vitest is a committed dependency gating 305 tests on every push. Playwright is deliberately not a dependency at all — installed ad hoc for a script that runs a few times a year.
MIT permits selling software built on it, unconditionally. The obligation is the notice — and this storefront's 551 packages show what that costs in practice.
The default scale, how v4 custom breakpoints work in @theme, and what a census of one production codebase says about where the seams actually land.
Our own home page had the container context and none of the queries — so a card loses a third of its width at lg while its padding never moves. The arithmetic and the fix.
Sticky positioning without a scroll listener, aria-current for the active row, and the hard-coded active-state bug that only appears when you add a second page.
React ships a 221 KiB react-dom runtime; Svelte compiles it away instead. Why that gap matters less once Server Components mean most of a page never hydrates.
38 aria-label attributes, zero tooltip libraries. What this codebase actually uses to name icon-only buttons, and the one place a real tooltip earns its keep.
Thirteen redirects, eight files, zero next.config.ts entries. Why every redirect() call here depends on auth state config-based redirects cannot see.
Our own FAQ accordion had two of five WAI-ARIA requirements, on a page that already scored Lighthouse accessibility 100. The audit and the fix that shipped with it.
Our dashboard tabs had the roles and none of the wiring — no aria-controls, no tabpanel, no roving tabindex. The audit, why no scanner caught it, and the fix that shipped with this post.
A dynamic segment is easy. Deciding which URLs deserve one is the work. Read from a real build: 288 prerendered pages, the dynamicParams asymmetry we found, and why seven hub pages stayed files.
One @custom-variant line replaces darkMode: 'class'. Then you choose: dark: at every call site, or tokens under .dark. We picked the first 480 times — and found nothing can turn it on.
Agency guides price the build. The recurring bill is set by four architectural choices — and here they are itemised from a live storefront: 111 products, 288 static pages, 13 dependencies.
Our 8,114-line, 111-product catalog is document-shaped — and lives in a typed array, not a database. The commerce layer is relational, on Postgres. Why the split decides, not the app.
output: "standalone" keeps the image small; the trap is that NEXT_PUBLIC_ vars are baked in at build time, not read at container start. This repo's own env-var warning shows exactly why that matters.
This storefront ships zero UI-library dependencies across 91 hand-rolled components. The real tradeoff — bundle cost against accessibility coverage you skip rebuilding — measured against our own code.
Our entitlement engine is 75 lines with two status states; our LemonSqueezy webhook recognizes two event names. That's not a shortcut — it's the real cost gap between the two pricing models.
notFound() throws rather than returns, so a try/catch upstream can silently swallow it. We rebuilt the site and read the generated 404 output to confirm what Next.js actually ships.
Redeclaring a default Tailwind color name overrides it everywhere; a new name just adds one. This codebase's own token file shows both, plus a scale that was never built for error states.
An audit of every loading state in this codebase — the shaped placeholder that replaces a skeleton, and the derived-state trick that stops one flashing.
Rewriting in middleware does not make your pages dynamic — both variants stay prerendered. What it really costs is the shared cache and every request's critical path.
Static export can't run middleware, a restart isn't a rebuild, and a missing env var in a proxy file breaks the whole site on first request. Our own go-live checklist.
A filterable listings grid, agent profiles, and a valuation funnel — not a demo request. What a real-estate landing template actually ships, using our own catalog as the reference.
Login, signup, password reset and contact all run on FormData, a Server Action and useActionState — zero form libraries in package.json. Here's the actual validation code.
A real accessible React modal with zero dependencies — focus trap, ARIA dialog role, restore-on-close, and the iframe-focus-escape bug most modal libraries never mention.
Six RLS policies, all for select, zero write policies anywhere. What authorization looks like when it's a property of the table instead of a service beside it.
A passive CSS marquee and a navigable translated-track gallery, both dependency-free — plus the missing prefers-reduced-motion guard this audit found and fixed.
187 prerendered pages, checked against the build output rather than the pattern: title and description lengths, what generates them, and where the trade-off is deliberate.
A fresh build of this site: 246 prerendered routes, 8 dynamic ones, and the one live chat widget that still has to hydrate on the client.
The fee gap is $0.82 on a $39 order. What it buys is a tax registration you no longer hold — and one silent failure that charges the buyer and grants nothing.
Five commands, three greps, and the defects they caught on a site that already looked finished — including a licence page selling a tier that no longer existed.
One route file, force-static, and eight lines of helpers. RFC-822 dates, five escaped characters, and the absolute-URL bug that reached our production markup.
We grepped the build: two public values in one client file each, three secrets in none. The trap is not the prefix rule but that the value freezes at build time.
Choose by the boundary the customer receives, not by code sharing. The cost is not merging — it is the generated index, and ours already had four bad rows.
The score is a claim about one URL. Three defects our own audit found only by measuring every page, three more no scanner reports, and a ten-minute test you can run before buying.
Six row-level policies, all of them SELECT, and no write policy for any signed-in role. Where a permission decision belongs, and why the UI check is a comment.
The capture is four lines; deciding what is in frame is the work. Nav-driven page discovery, the is-this-the-product check, and the proxy bug that blanks every Chromium request.
24 of this site's 91 components are client components, and not one is an atom. The cost is never the component — it is the module graph it drags across the boundary.
An audit of our own site against the standard, including the two places it falls short and the checklist item we deliberately do not ship. The unit of success is a passage, not a page.
Naming five layers changes nothing. Ordering their imports changes everything — including where the client boundary lands, which on this site was never designed at all.
Renaming the file is a third of the migration. The exported function has to change too, keeping both files is a build error, and the proxy runs on Node rather than the Edge.
Most comparisons pit a WordPress theme against a from-scratch Next.js build. Compare theme to template instead and the real trade turns out to be who edits the copy.
Rebranding a template is an hour. Keeping it mergeable when v1.3.0 ships is the part nobody plans for. Here is the edit order that keeps both possible.
A hand-maintained slug list left 24 of our products on no category page at all, and nothing failed. Here is the derived hub-and-spoke build that fixed it.
Four App Router features opt a page out of static rendering with no error and no warning. The only place it shows is one character in the build output.
Both are public POST endpoints. The choice is settled by three things only Route Handlers have: the raw body, a real URL, and the status code.
A 14-day, no-download refund policy and the four-state engine behind it — including the attribution 'fix' that opens a download-then-refund bypass.
Two emails, opposite failure policies — and the fire-and-forget bug that sends mail perfectly in dev and silently drops it in production.
111 products, no search index. The predicate, the rule that stops results looking broken, and the bundle trade we took on one page only.
A banner-gated tool measures the subset of visitors who agreed to be measured. The cookieless trade, the typed event union, and the CSP origin we nearly added for nothing.
Authenticate, verify, validate, authorize, rate-limit and record — then sign. The atomic limiter that closes the count-then-insert race, and what fails closed versus open.
Prerendered HTML cannot know what one browser saved. An empty server snapshot, the getSnapshot cache that stops an infinite render loop, and why theme is the opposite problem.
Judge it on what you cannot fix after buying. Run Lighthouse on mobile, audit the inner pages, count the routes that actually exist — and read the licence for seats, not domains.
The documented nonce recipe turns every route it touches dynamic. The static-safe policy we ship instead, what 'unsafe-inline' really costs, and what the header still blocks.
An editorial-workflow decision wearing an architecture decision's clothes. What MDX buys you in CI, the Turbopack plugin trap, and the four cases where a CMS simply wins.
Virtualizing a four-slide carousel removes three product images from the HTML. Mount every slide, starve the off-screen ones, and the ARIA that a carousel actually needs.
Every other comparison asks which framework to build in. This asks which folder to open after you have bought the design — and why the licence usually covers both.
Authorization is where they differ most and what you cannot change later. Row-Level Security vs Security Rules, the Server Component fit, and the 68 KiB our own auth SDK cost.
Change the technology or the URLs, never both. The redirect map, the internal-link diff that put 38 of our own pages in “Discovered — not indexed”, and the 90 days after.
It is a navigation landmark, not an application menu — and the ARIA menu pattern most tutorials copy removes your nav from every screen reader's link list. Six behaviours, eighty lines.
Virtualization forces the table into a Client Component and hands back the sorting, filtering and accessibility the server did for free. The three options, and the hybrid worth using.
Shopify's fee buys a checkout, tax handling and an ops backend — not hosting. The four jobs you take on by leaving, the headless hybrid, and when owning the frontend pays.
Both score 100 on desktop. We blocked every image on our own site and LCP moved 46ms — the mobile gap is React hydration, not bytes. What that means for the choice.
Size and colour variants render fine from a typed file — until stock has to be authoritative. The option matrix, the URL-driven picker, the canonical, and where it breaks.
A closed image set does not need a request-time optimiser. The sharp build script, the plain-img markup, and the lazy LCP image that cost us 1.67s of load delay.
The launch-speed argument mostly disappears once you compare template to template. What is left is who edits the copy, what each locks in, and three cases where Webflow wins.
No API route, no client fetch, and it still submits with JavaScript off. Validation, a honeypot, a rate limit that survives serverless, and the from-address trap that kills deliverability.
A metadata route under a dynamic segment needs its own generateStaticParams, or every card renders per request. That trap, the Satori CSS subset, fonts, and how to verify a crawler sees it.
animation-timeline replaces the scroll-animation library category — behind two guards. The Tailwind v4 setup, the element you must never fade in, and when IntersectionObserver still wins.
A cookie, a database row, or React Context? The three cart architectures compared, the add-to-cart Server Action, and the badge that quietly breaks static rendering.
Filters in useState cannot be shared, bookmarked, or server-rendered. How to read them from searchParams — and which filtered URLs to let Google crawl.
Product needs only one of offers, review or aggregateRating — so a truthful offer qualifies you without inventing reviews. The shape, the validation, the manual-action trap.
Four to nine weeks of UI work sits between an installed framework and a presentable app. What a template saves, what it does not, and when building wins.
Commercial template licenses limit developer seats and end-user monetization — not domains. Here is how to read the clauses and pick the right tier.
The App Router is the default for new projects, but the Pages Router is not deprecated. Here is what actually changed and when migrating is not worth it.
Tailwind v4 tokens compile to real CSS custom properties, so one build can serve every tenant's brand. The override pattern, contrast handling, and the pitfalls.
Static rendering, LCP on the hero, and a small client bundle — plus the accessibility bugs our own Lighthouse audit caught that code review missed.
A working admin dashboard in Next.js 16 and Tailwind CSS v4 — App Router layouts, a CSS-first theme, an accessible sidebar, and the server/client split that keeps it fast.
Tailwind v4 moved configuration into CSS and dropped tailwind.config.js. Here is what breaks, what the @theme block replaces, and the order to migrate in.
The theme flash is a rendering-order problem, not a CSS one. Here is the blocking-script pattern that fixes it in a static Next.js app, with the full code.
Wire a Next.js app to LemonSqueezy end to end — hosted checkout, a signature-verified webhook, and the entitlement check that gates the download.
Both ship excellent dashboards. The decision comes down to where your data lives, whether you need SEO, and who deploys it — not to raw performance.
A fair comparison of two mature CSS frameworks for admin UIs — component coverage, customization ceiling, bundle size, and the team each one suits.