Securing the Website
Securing the website is a crucial step in the deployment process. It ensures that the website is protected from unauthorized access and malicious attacks.
Fundamentals of Securing any NextJS Website
In general we can secure a website in 3 layers:
| Layer | Purpose |
|---|---|
| Server auth | The source of truth for security and data access |
| Middleware auth | Route-level protection (block unauthenticated requests early) |
| Client auth | UX polish only (nice UI, not security) |
A good rule: client decides what looks nice, server decides what is true.
We will start with the least secure and work upwards.
Client Auth Layer
Think of this as UX polish (not security). A client-side login state is great for things like:
- Changing a header button from Login --> Account
- Showing a different homepage hero for logged-in users
- Personalised greetings (eg. “Welcome back”)
Important notes
Client auth is a great doormat. It is not a lock however, for SEO it might render as it's loading state or null because of SSR.
You should not hide sensitive data using this method. Also running 10 components all running their own auth checks could cause performance issues.
Example of "Sign in" or "My Account" button
import { useLoginState } from "@/lib/supabase/loginState"
function MyHeader() {
const { state } = useLoginState()
return <>
{state === "loading" ? (
<Button variant="outline" disabled >
<FaSpinner className="animate-spin" />
</Button>
) : state === "signedIn" ? (
<Button variant="outline" href="/account">
Account
</Button>
) : (
<Button href="/login">Sign in</Button>
)}
</>
}
Server Auth Layer
Server auth is where real security lives. This means:
- Reading session/user from server-side cookies
- Redirecting from protected pages on the server
- Enforcing database access via RLS (Supabase recommended)
In this project, server auth lives here:
src/lib/supabase/loginState.server.ts
This is a solid place to read the current auth state on the server and use it to protect pages. It is appropriate for sensitive page protection because it runs server-side and does not rely on browser-only state.
Example: Protect a page server-side (App Router)
// app/my-dashboard/page.tsx
import { redirect } from "next/navigation";
import { getServerLoginState } from "@/lib/supabase/loginState.server";
export default async function MyDashboardPage() {
const { signedIn } = await getServerLoginState();
if (!signedIn) {
redirect("/login?next=/my-dashboard");
}
return <div>Private dashboard</div>;
}
Using middleware.ts to secure multiple pages
Adding a /src/middleware.ts file to secure entire routes is a great way to secure multiple pages. The example below would protect anything in
the /account/ route and anything inside it. i.e :
/account/
/account/settings
/account/profile
/account/billing
/account/security
./account/... any other pages inside the /account/ route
middleware.ts - secure entire routes
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
const LOGIN_PATH = "/login";
export async function middleware(req: NextRequest) {
let res = NextResponse.next({
request: { headers: req.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return req.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
res.cookies.set(name, value, options);
});
},
},
}
);
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
const loginUrl = req.nextUrl.clone();
loginUrl.pathname = LOGIN_PATH;
loginUrl.searchParams.set("redirectTo", req.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return res;
}
export const config = {
matcher: [
"/account/:path*",
// '/another-secure-area/:path*'
],
};
Should I use middleware and server auth for all my pages?
Yes and No.
Yes, if your pages are ultra sensitive and you need to be 100% sure the user is logged in.
Middleware could potentially be misconfigured, so having both is a good idea.