Protected Route
A copy-paste React access screen for route-level guarding — React Router or similar, with an optional custom denial state. Zero dependencies.
Best used for Route-level guarding in React Router or similar, especially with a custom denial screen.
Meant for a React Router element or any layout-level guard where "locked" sometimes means "show a
different route entirely" rather than "prompt inline."
Minimal setup
<ProtectedRoute expectedHash={process.env.NEXT_PUBLIC_KNOCK_HASH}>
<Dashboard />
</ProtectedRoute>
- components
- knock-codes
- core
- react
"use client";
import type { ReactNode } from "react";
import { KnockCodes, type KnockCodesProps } from "./KnockCodes.tsx";
import { GateSession } from "./KnockCodesContext.tsx";
export interface ProtectedRouteProps extends KnockCodesProps {
/**
* Rendered instead of the default PIN prompt while locked — e.g. a
* redirect notice paired with your router's own navigation. When omitted,
* behaves exactly like `<KnockCodes>`.
*/
unauthorizedFallback?: ReactNode;
}
function FallbackGate({ unauthorizedFallback, children, labels: _labels, variant: _variant, className: _className, ...config }: ProtectedRouteProps) {
return (
<GateSession config={config}>
{({ ready, state }) => {
if (!ready) return null;
return <>{state === "unlocked" ? children : unauthorizedFallback}</>;
}}
</GateSession>
);
}
/**
* `<KnockCodes>` for route-level guarding — a React Router `element`, a
* layout-level redirect guard — with one addition: an optional
* `unauthorizedFallback` for routes that should show something other than
* an inline PIN prompt while locked (e.g. a "redirecting…" notice).
*
* Inside a `<KnockCodesProvider>`, shares that session rather than creating
* a second hook instance.
*/
export function ProtectedRoute({ unauthorizedFallback, ...props }: ProtectedRouteProps) {
if (unauthorizedFallback === undefined) return <KnockCodes {...props} />;
return <FallbackGate {...props} unauthorizedFallback={unauthorizedFallback} />;
}
Add this block to your project
Recommended
npx shadcn@latest add @knock-codes/protected-routeAlso installs
- Knock Codes Core
- Knock Codes Types
- useKnockCodes
- cx (classname helper)
- Gate Wrapper
- PIN Input
- Knock Codes
These install together as one atomic unit — even a presentational or read-only piece needs the full verification stack (hook, types, core) behind it to actually run.
Files created (12)
- components/knock-codes/core/hash.ts
- components/knock-codes/core/verify.ts
- components/knock-codes/core/session.ts
- components/knock-codes/core/storage.ts
- components/knock-codes/react/types.ts
- components/knock-codes/react/useKnockCodes.ts
- components/knock-codes/react/KnockCodesContext.tsx
- components/knock-codes/react/cx.ts
- components/knock-codes/react/GateWrapper.tsx
- components/knock-codes/react/PinInput.tsx
- components/knock-codes/react/KnockCodes.tsx
- components/knock-codes/react/ProtectedRoute.tsx
Other ways
GitHub shorthand
npx shadcn@latest add trivedi-vatsal/knock-codes/protected-routeCopy the files by hand
- Open the Code tab in the preview above.
- Create each path listed below in your project and paste its contents in.
- Do the same for anything listed under “Also installs”, if present.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| expectedHash | string | — | SHA-256 hex hash to verify against, for local mode. |
| verify | VerifyFn | — | Custom async verification function, for server mode. |
| children * | ReactNode | — | Rendered once unlocked. |
| unauthorizedFallback | ReactNode | — | Rendered instead of the default access-code prompt while locked. Omit for Knock Codes's default behavior. |
| labels | KnockCodesLabels | — | Overrides for every user-facing string. |
| variant | "page" | "inline" | "page" | Outer positioning. |
Exactly one of expectedHash or verify is required.
Accessibility
Identical to Knock Codes when unauthorizedFallback is unset. When it is set, make sure whatever you pass as the fallback is itself accessible — this block doesn't manage focus for you across a route transition.
Customization
Pass `unauthorizedFallback` to render something other than an inline access-code prompt while locked — pair it with your router's own navigation for a redirect-on-lock pattern, or pass an `<AccessDeniedScreen>` for a hard-denial route.
Need a hash? Use the hash generator on Getting Started — computed locally, never sent anywhere.
The honest version
Knock Codes stops casual visitors, search engines, and forwarded links. Local mode does not stop anyone who opens DevTools — the hash ships in your client bundle by design. Server mode (swap one prop) hides the hash from the client; children you already bundled are still in the JavaScript, and a forged session works unless you wire validateSession. A velvet rope, with an optional real lock. Never marketed as more than that.
Blocks that pair well with this one
These combine naturally with this block, whether as a shared shell, a shared session, or a common fallback.
- Knock CodesA copy-paste React access screen for gating a whole page or app root — local hash or server verification, one prop swap between them. Zero dependencies.
- Protected LayoutA copy-paste React access screen for a Next.js layout or app root — header and footer stay visible while locked. Zero dependencies.
- Access Denied ScreenA copy-paste React denial screen for a hard-locked route or a custom Protected Route fallback — no access-code form, no retry. Zero dependencies.