All posts
13 min read

33 shadcn Error Page Blocks: 404, 500, 403 and Every Other Way a Request Can End

A production error page is a status code plus a way forward. This is the new Error category: thirty-three drop-in blocks built on shadcn/ui for 404, 500, 403, 410, 429, 451, 503 and offline states, with a guide to which status code each one belongs to. Eleven of the best, explained by use case.

error404shadcnreactnextjs

Every other page on a site gets designed. The error page gets a centered 404 and a link to the homepage, written in the last hour before launch, and then it quietly serves more traffic than most of the marketing pages that took a week. Broken inbound links, a renamed docs URL that someone bookmarked two years ago, an expired magic link, a rate limit during a launch spike, a card that failed to renew. Each of those is a different problem with a different fix, and each of them lands on the same apologetic dead end.

The new Error category treats that dead end as a real screen. Thirty-three blocks, six of them free, covering not only the 404 but the whole family of states a request can end in: server failures, permission walls, expired links, rate limits, planned downtime, billing lockouts and lost connections. Rather than walk through all of them, this post explains eleven, grouped by the situation they belong to.

A status code is a promise, and the page has to keep it

Before design, semantics. The status code is what search engines, monitoring tools and browsers actually read. The page is what humans read. When those two disagree, something downstream breaks.

CodeMeansReach for it when
400The request itself was malformedA query string or payload cannot be parsed at all
401Not authenticatedThere is no valid session, so signing in would fix it
402Payment requiredA subscription lapsed or a balance is unpaid
403Authenticated, still not allowedSigning in again changes nothing; a permission has to be granted
404Not foundThe address may or may not have existed, and may exist later
410Gone, on purposeThe content was removed deliberately and is not coming back
429Too many requestsA rate limit was hit, and waiting will clear it
451Unavailable for legal reasonsLicensing or a legal order blocks this region
500Something broke on the serverThe failure is yours, not the visitor's
503Temporarily unavailablePlanned maintenance or an overloaded service

Four distinctions are worth the extra minute:

401 is not 403. A signed-out visitor gets 401, and the fix is a sign-in form. A signed-in visitor without permission gets 403, and showing them a sign-in form is a small insult, because they are already signed in. The page for the second case has to say who they are and how access gets granted.

410 is not 404. A 404 says "nothing here, maybe later." A 410 says "this existed, we removed it, stop asking." Search engines treat it as the stronger removal signal, so use it for content you retired on purpose and keep the URL alive with an explanation rather than letting it rot into a generic miss.

503 is not a 200 with a maintenance message. Serving your maintenance page with a success status tells every crawler that your content is now a maintenance notice. A 503 with a Retry-After header tells them to come back later and leaves your rankings alone.

A soft 404 is the most common mistake of all. Rendering "page not found" while returning 200 means the page reads as real content to everything except a human. If the page says not found, the response has to say it too.

How this maps onto Next.js

In the App Router, not-found.tsx renders with a real 404, and calling notFound() from next/navigation routes there. error.tsx is a client component that receives error and a reset function, which is exactly what the retry buttons in these blocks are built to call. Statuses without a file convention, 410 and 429 among them, are set in a route handler or in middleware while the same block renders the body.

What installs

Every block is one React component, and the CLI resolves the whole tree in a single command:

bash
npx shadcn add "https://ui.beste.co/r/error15?email=YOUR_EMAIL&license_key=YOUR_KEY"

That drops the block at components/beste/block/error15.tsx, plus the small primitives it renders through, and nothing else. No runtime dependency on us, no license check in your bundle, no service to sign up for. The free blocks skip the query string entirely. If you are on Base UI rather than Radix, swap /r/ for /r-base/ and you get the Base UI build of the same component, and the installation docs cover the license setup once.

The 404 is not one page

Most teams ship a single 404 for the whole site. That is a reasonable start and a poor finish, because a missing product, a renamed article and a mistyped marketing URL are three different failures with three different exits.

The baseline still matters. error1 is the version most sites need on day one: the code, the message, two actions and a short row of the pages people actually want.

Centered 404 Message
View block
Loading preview…
error1: the baseline 404. An oversized code, a heading, an explanation, two buttons and a row of popular links. Free to install.

Once a site is large enough that no shortlist of links can cover it, search beats guessing. error2 puts a real search field directly under the message and hands the query to a callback, so it routes into whatever search you already run.

404 With Site Search
View block
Loading preview…
error2: search-first. A controlled field passes the trimmed query to onSearch, above a list of suggested destinations. Free to install.

Documentation sites have a narrower problem: the page did not vanish, it moved. error9 prints the address that failed, struck through in monospace, and offers the closest match underneath, which turns a dead bookmark into one click instead of a search session.

404 Did You Mean
View block
Loading preview…
error9: did you mean. The requested path is echoed struck through, with the nearest matching page linked below it. Free to install.

A store has the opposite problem: the visitor knew exactly what they wanted and it is gone. Sending them to the homepage ends the session, so error11 keeps the intent alive by putting four products under the apology.

404 With Product Recommendations
View block
Loading preview…
error11: the retired product URL. Shopping actions above a four-up recommendation grid with prices and optional compare-at prices.

Publishers get the same shape with different content. error12 fills the space under a dead post URL with the three articles people are actually reading, and error32 does it in a quieter editorial layout for a renamed essay. For a site too large for either, error13 turns the 404 into a full directory, four columns of grouped sections, so a visitor can land where they meant to go without touching navigation.

404 With Site Directory
View block
Loading preview…
error13: the 404 as a sitemap. Four columns of grouped section links for sites where a shortlist of suggestions cannot cover the surface.

Two more worth knowing about: error8 counts down and then calls onRedirect, which suits a retired campaign URL you want to move on from automatically, and error10 is the full-screen version with a background photograph for marketing sites where the 404 still has to look like the brand.

When it is not the visitor's fault

A 500 is an apology with a job to do. The visitor cannot fix anything, so the only useful thing the page can give them is something support can act on. error15 puts a reference, a timestamp, the affected service and the current state in a table, and pairs it with a retry button that maps straight onto the reset function from a Next.js error.tsx.

500 Incident Report
View block
Loading preview…
error15: the 500 as an incident report. A reference, timestamp, service and state in a bordered table, with a retry button wired to onRetry.

Planned downtime is a different conversation. Nothing is broken, you did this deliberately, and the two questions are how long and how will I know. error17 answers both: three cells for the window and a working notify field for the all clear. Serve it with a 503 and a Retry-After, and both your visitors and your crawlers are told the truth.

Maintenance Window Notice
View block
Loading preview…
error17: the maintenance window. Start, expected return and affected services in bordered cells, above a notify-me field with a success state.

Rate limits are the state most often shipped as a raw JSON error. A 429 is genuinely temporary, so the page should say when the limit lifts rather than inviting a refresh loop. error25 counts down from the value you pass, and only when it reaches zero does the retry button appear in its place.

Rate Limit Cooldown
View block
Loading preview…
error25: the rate limit cooldown. A live counter holds the slot until the limit lifts, then swaps itself for the retry button. Feed it your Retry-After value.

When the failure is the network rather than the server, error31 shows what is still waiting to send on the device and retries on a loop, raising the attempt number each time, so an offline app feels paused rather than broken.

When the door is locked

Permission pages fail in a specific way: they tell people what they cannot do and stop there. Every block in this group is built around the next step instead.

error16 is the 403 for team products. It shows the account that is signed in, offers an account switch for the classic wrong-workspace mistake, lists what actually grants access, and puts a request button next to it.

403 Access Request
View block
Loading preview…
error16: the 403 for team tools. The signed-in account, an account switch link, the requirements that unlock the page and a request action.

Expired sign-in links belong to the same family, and they are the highest friction moment in any passwordless flow. error18 prints the expiry timestamp, explains the three reasons a single-use link stops working, and turns the resend button into a confirmation the moment it is pressed, which stops the queue of five links people request when nothing appears to happen. error27 covers the timed-out session, including the line that matters most: the draft was saved.

Two commercial variants round the group out. error26 is the 402 after a failed renewal, pairing the unpaid invoice with a list of everything that stays untouched while the account is read only, and error28 is the 451 for licensing limits, naming the visitor's region and showing which markets are live.

Reassurance is a feature

Lockout pages are where support tickets are born. The three lines that prevent most of them are the same three every time: what is preserved, what happens next and who can change it. error26 lists what stays safe during a billing lockout, error27 confirms the autosave, and error31 states that queued changes survive a closed tab.

Every value on the screen is a prop

The rule behind this category is the same one behind the dashboard set: if you can read it, you can pass it. Every block renders its full demo with zero props, and each prop is optional, so real content replaces the demo one field at a time.

tsx
"use client";

import { Error15 } from "@/components/beste/block/error15";

export default function ErrorPage({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <Error15
      label="Server Error"
      code="500"
      heading="Something broke <strong>on our side</strong>."
      description="Your request never reached the finish line. The team has been paged."
      labels={{ retry: "Run it again", detailsTitle: "Incident details" }}
      details={[
        { title: "Reference", value: error.digest ?? "unavailable" },
        { title: "Service", value: "Checkout API" },
        { title: "Status", value: "Under investigation" },
      ]}
      link={{ label: "Follow the status page", href: "/status" }}
      onRetry={reset}
    />
  );
}

Every block also exports its demo object, error15Demo here, which doubles as documentation: the exact shape your data needs, in a file you can open and edit.

The details you would have had to get right yourself

The eleven at a glance

Each block links to a live, full-screen preview on its page. These are the highlights; the Error category holds all thirty-three.

BlockStatusBest for
error1404The baseline every site needs first
error2404Sites too large for a link shortlist
error9404Documentation with moved URLs
error11404Retired product pages in a store
error12404Unpublished or renamed articles
error13404Large sites that need a full directory
error15500Failures support has to trace
error16403Permission walls in team products
error17503Planned maintenance windows
error18410Expired magic links and invites
error25429Rate limits with a real cooldown

The first three are free to install and try. The rest are part of Beste UI Pro, alongside the onboarding, coming soon and landing page sets.

Where these get discovered

We publish the whole catalog as a structured, machine-readable feed so search engines and assistants can index it directly, which is how a query like "shadcn 404 page" surfaces a component instead of a screenshot gallery. Our MCP server exposes the same catalog to coding assistants, so an agent working in your repo can install the right error page without leaving the editor.

Design the page you hope nobody reaches

Error pages are the cheapest trust you will ever buy. A visitor who hits a dead product URL and finds four alternatives keeps shopping. A customer who hits a billing wall and reads exactly what is preserved opens a ticket instead of a chargeback. An engineer who hits a 500 with a reference code files something you can act on. Pick the status code first, pick the block that matches the situation second, and give the page the same care you gave the one that sent them there.

Cover every state, not just the 404

All thirty-three error blocks and every other Pro block are included in one license. Install them from the CLI and own the code.

Get Pro
Markdown version