Tailwind Dropdown: One Real Menu, and a Second One Rejected in a Comment
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.
Practical write-ups from the team that builds the templates — architecture decisions, migration notes, and honest framework comparisons.
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.
Five tables, five 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.