How to Integrate Supabase Auth with Astro: Complete Guide
Step-by-step guide to integrating Supabase Auth with your Astro website. Setup, configuration, and best practices.
How to Integrate Supabase Auth with Astro: Complete Guide
Supabase Auth provides a full authentication system built on top of PostgreSQL with support for email/password, magic links, social OAuth providers, and phone-based authentication. Since Supabase also serves as your database, you get authentication and data access from a single service. Pairing Supabase Auth with Astro's server-side rendering gives you secure session management and protected routes without third-party session stores.
This guide covers setting up Supabase Auth in an Astro project with server-side session handling using cookies.
Prerequisites
Before starting, make sure you have:
- Node.js 22.12.0 or higher (Astro v6 dropped Node 18 and Node 20 support entirely, so neither works anymore). Odd-numbered Node releases are not supported either.
- An existing Astro project (Astro v6 is current; on-demand rendering is enabled per route or site-wide, see Configuration below)
- A Supabase project created at supabase.com
- Your Supabase project URL and API key (found in Project Settings > API Keys)
- Basic familiarity with cookies and session management
Installation
Install the Supabase JavaScript client, the SSR helper package, and the Astro Node adapter:
npm install @supabase/supabase-js @supabase/ssr @astrojs/node
This pulls the current versions: @supabase/supabase-js 2.106.2, @supabase/ssr 0.10.3, and @astrojs/node 10.1.2 (verified on npm, 2026-05-29). The @supabase/ssr package provides cookie-based session management designed for server-rendered frameworks like Astro, and it replaces the older @supabase/auth-helpers-* packages, which Supabase has officially deprecated. Do not mix @supabase/auth-helpers with @supabase/ssr in the same app, since that causes session conflicts.
If you used npm install above, the Node adapter is already present. You still need to register it with Astro, which the Configuration step below does. Alternatively, let the Astro CLI install and wire it for you:
npx astro add node
Configuration
Environment Variables
Add your Supabase credentials to .env:
PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here
The PUBLIC_ prefix makes these available in both server and client contexts in Astro.
Creating the Supabase Client
Create a server-side Supabase client that manages sessions through cookies:
// src/lib/supabase.ts
import { createServerClient, parseCookieHeader } from '@supabase/ssr';
import type { AstroCookies } from 'astro';
export function createSupabaseClient(request: Request, cookies: AstroCookies) {
return createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('cookie') ?? '');
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
cookies.set(name, value, options);
});
},
},
}
);
}
Astro Config
A note on output modes. The old output: 'hybrid' value was removed in Astro 5 and does not exist in Astro 6. Today 'static' is the default (every page is prerendered), and 'server' makes pages render on demand by default. Either way you need a server adapter for any on-demand route, because auth pages and API routes must run at request time to read and write cookies.
The simplest setup for an auth-heavy app is full output: 'server' with the Node adapter:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
});
If your site is mostly static and only a handful of routes need authentication, keep the default 'static' output, still register the adapter, and opt individual pages or endpoints into on-demand rendering with export const prerender = false at the top of each file. This replaces the role the removed 'hybrid' mode used to play.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
// 'static' is the default, shown here for clarity
output: 'static',
adapter: node({ mode: 'standalone' }),
});
---
// src/pages/dashboard.astro
export const prerender = false; // render on demand so cookies can be read
---
Common Patterns
Authentication Middleware
Create middleware to make the user session available on every request:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { createSupabaseClient } from './lib/supabase';
export const onRequest = defineMiddleware(async ({ request, cookies, locals }, next) => {
const supabase = createSupabaseClient(request, cookies);
const { data: { user } } = await supabase.auth.getUser();
locals.user = user;
locals.supabase = supabase;
return next();
});
This gives every page and API route access to Astro.locals.user and Astro.locals.supabase.
Sign Up and Login Pages
Create a login page with email/password authentication:
---
// src/pages/login.astro
if (Astro.locals.user) {
return Astro.redirect('/dashboard');
}
---
<html>
<body>
<h1>Log In</h1>
<form method="POST" action="/api/auth/login">
<label>Email <input type="email" name="email" required /></label>
<label>Password <input type="password" name="password" required /></label>
<button type="submit">Log In</button>
</form>
<p>No account? <a href="/signup">Sign up</a></p>
</body>
</html>
Login API Route
// src/pages/api/auth/login.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createSupabaseClient(request, cookies);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
});
}
return redirect('/dashboard');
};
Sign Up API Route
// src/pages/api/auth/signup.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const supabase = createSupabaseClient(request, cookies);
const { error } = await supabase.auth.signUp({
email,
password,
});
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
});
}
return redirect('/check-email');
};
Protecting Pages
---
// src/pages/dashboard.astro
const user = Astro.locals.user;
if (!user) {
return Astro.redirect('/login');
}
---
<html>
<body>
<h1>Dashboard</h1>
<p>Logged in as {user.email}</p>
<form method="POST" action="/api/auth/logout">
<button type="submit">Log Out</button>
</form>
</body>
</html>
Logout Route
// src/pages/api/auth/logout.ts
import type { APIRoute } from 'astro';
import { createSupabaseClient } from '../../../lib/supabase';
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const supabase = createSupabaseClient(request, cookies);
await supabase.auth.signOut();
return redirect('/login');
};
Troubleshooting
User is null even after login: The @supabase/ssr package relies on cookies being set correctly. Make sure your setAll function in the client factory actually calls cookies.set() for each cookie. Also verify you are using getUser(), which revalidates the token against the Auth server, and not getSession(), which only reads the local token. The current Supabase server-side guide goes a step further and recommends getClaims(), which verifies the JWT signature against your project's published public keys on every call, so it is the strongest choice for protecting pages and data inside server code.
Cookies not persisting across requests: Check that your Astro adapter supports cookie manipulation. The Node.js adapter works by default. If deploying to Cloudflare or Vercel, use their respective adapters.
OAuth redirects failing: When using social login providers, configure the redirect URL in both Supabase (Authentication > URL Configuration) and the OAuth provider's dashboard. The callback URL should be https://your-project.supabase.co/auth/v1/callback.
"Invalid Refresh Token" errors: This happens when the refresh token stored in cookies has expired or been revoked. Clear the user's cookies and ask them to log in again. Set reasonable token expiry times in Supabase Authentication settings.
Common Errors and Fixes
parseCookieHeader receives null and crashes or returns nothing. request.headers.get('Cookie') returns null when no cookies are present, and passing null into parseCookieHeader produces a type error or an empty result. Always supply a string fallback with ?? '', exactly as the client factory above does. This is the single most common first-run failure on a fresh Astro plus Supabase setup.
get, set, and remove, or parse and serialize, throw deprecation warnings. Earlier @supabase/ssr examples (and the auto-generated snippet in the Supabase dashboard "Connect" panel) used per-cookie get/set/remove methods and the parse/serialize helpers. Those are deprecated. The current API is the bulk pair getAll() and setAll() on the cookies object, paired with parseCookieHeader and serializeCookieHeader. Use the getAll/setAll shape shown in this guide and ignore the dashboard's older generated code.
Mixing @supabase/auth-helpers and @supabase/ssr. All @supabase/auth-helpers-* packages are deprecated and unmaintained, with @supabase/auth-helpers-nextjs frozen at 0.15.0. If you migrated an older project, remove every auth-helpers import. Supabase warns explicitly that running both packages in one app causes authentication issues. The migration mapping is createServerComponentClient, createRouteHandlerClient, and createMiddlewareClient all become createServerClient, and createClientComponentClient becomes createBrowserClient.
output: 'hybrid' build error. If you copied an older config, Astro 6 rejects 'hybrid' because it was removed in Astro 5. Use 'static' (the default) plus export const prerender = false on the routes that need a server, or 'server' for site-wide on-demand rendering. Either path still requires a registered adapter.
Cookies set during login do not persist to the next request. When the auth cookie appears only after a second login (a known issue on the OTP and code-exchange flows), the cause is usually a setAll implementation that silently drops cookies or swallows errors. Make sure setAll iterates every entry in cookiesToSet and calls cookies.set(name, value, options) for each one, and that your middleware runs on the request that performs the exchange so the refreshed cookies are written to the response.
Cookies not persisting across requests at all. Confirm your Astro adapter supports cookie manipulation. The Node adapter does so by default. When deploying to Cloudflare or Vercel, install their respective adapters (@astrojs/cloudflare, @astrojs/vercel) rather than the Node one.
Official Docs and Examples
- Supabase Astro Auth quickstart: https://supabase.com/docs/guides/auth/quickstarts/astrojs
- Supabase server-side client creation guide (the
getAll/setAllreference): https://supabase.com/docs/guides/auth/server-side/creating-a-client - Supabase migration guide, Auth Helpers to the SSR package: https://supabase.com/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers
- Astro official Supabase backend guide: https://docs.astro.build/en/guides/backend/supabase/
- Astro on-demand rendering and adapters guide: https://docs.astro.build/en/guides/on-demand-rendering/
- Astro Node adapter reference: https://docs.astro.build/en/guides/integrations-guide/node/
- Example repo (community demo, Astro plus Supabase auth and database): https://github.com/kevinzunigacuellar/astro-supabase
- Walkthrough post (community, Astro plus Supabase SSR auth): https://mihai-andrei.com/blog/how-to-add-supabase-auth-to-astro/
Conclusion
Supabase Auth gives Astro projects a complete authentication system backed by PostgreSQL. The cookie-based SSR approach keeps sessions secure and server-validated, while the middleware pattern makes user data available across your entire application. Since Supabase also serves as your database, you can write Row Level Security policies that automatically restrict data access based on the authenticated user, creating a tight security model from authentication through to data access.
Sources
All versions and facts below were checked on 2026-05-29.
@supabase/supabase-jslatest version (2.106.2): https://registry.npmjs.org/@supabase/supabase-js/latest@supabase/ssrlatest version (0.10.3): https://registry.npmjs.org/@supabase/ssr/latestastrolatest version (6.4.2): https://registry.npmjs.org/astro/latest@astrojs/nodelatest version (10.1.2): https://registry.npmjs.org/@astrojs/node/latest- Supabase server-side client creation,
getAll/setAllandparseCookieHeaderpattern: https://supabase.com/docs/guides/auth/server-side/creating-a-client - Supabase Astro Auth quickstart, install command,
PUBLIC_env prefix, Node adapter,output: "server": https://supabase.com/docs/guides/auth/quickstarts/astrojs - Supabase migration guide, Auth Helpers deprecated in favor of
@supabase/ssr: https://supabase.com/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers - Supabase auth-helpers deprecation notice: https://github.com/supabase/auth-helpers
- Supabase issue 29910, deprecated
parse/serializeand per-cookie methods vsgetAll/setAll: https://github.com/supabase/supabase/issues/29910 - Supabase ssr issue 36, cookies not persisting until second login: https://github.com/supabase/ssr/issues/36
- Astro on-demand rendering, output modes (
staticdefault,server),prerender = false, adapters: https://docs.astro.build/en/guides/on-demand-rendering/ - Astro official Supabase backend guide: https://docs.astro.build/en/guides/backend/supabase/
- Astro Node adapter reference: https://docs.astro.build/en/guides/integrations-guide/node/
- Example repo (community demo): https://github.com/kevinzunigacuellar/astro-supabase
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.