How to Use Firebase with Astro: Complete Guide
Step-by-step guide to integrating Firebase with your Astro website. Installation, configuration, and best practices.
Firebase gives you a database, authentication, file storage, and hosting all in one package from Google. When paired with Astro, you can build full-stack apps where static pages handle the public content and Firebase powers the dynamic features like user accounts, real-time data, and file uploads.
Prerequisites
- Node.js 18+
- An Astro project (
npm create astro@latest) - A Firebase account and project (free Spark plan at firebase.google.com)
- Firebase CLI installed (
npm install -g firebase-tools)
Installation
Install the Firebase SDK:
npm install firebase firebase-admin
The firebase package is for client-side code. The firebase-admin package is for server-side operations in API routes.
Configuration
Create a Firebase project in the console, then grab your config values from Project Settings > General > Your apps.
Add credentials to your .env file:
# Client-side (public)
PUBLIC_FIREBASE_API_KEY=your_api_key
PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
PUBLIC_FIREBASE_PROJECT_ID=your_project_id
# Server-side (private)
FIREBASE_PRIVATE_KEY_ID=your_key_id
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxx@your_project.iam.gserviceaccount.com
Set up the client-side Firebase initialization:
// src/lib/firebase-client.ts
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: import.meta.env.PUBLIC_FIREBASE_API_KEY,
authDomain: import.meta.env.PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.PUBLIC_FIREBASE_PROJECT_ID,
};
const app = getApps().length ? getApps()[0] : initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
And the server-side admin setup:
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
const app = getApps().length
? getApps()[0]
: initializeApp({
credential: cert({
projectId: import.meta.env.PUBLIC_FIREBASE_PROJECT_ID,
privateKey: import.meta.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
clientEmail: import.meta.env.FIREBASE_CLIENT_EMAIL,
}),
});
export const adminAuth = getAuth(app);
export const adminDb = getFirestore(app);
Basic Usage: Authentication
Here is a login form using Firebase Auth on the client side:
---
// src/pages/login.astro
export const prerender = false;
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Login">
<form id="login-form" class="max-w-md mx-auto py-12">
<input type="email" id="email" placeholder="Email" required
class="w-full p-3 border rounded mb-4" />
<input type="password" id="password" placeholder="Password" required
class="w-full p-3 border rounded mb-4" />
<button type="submit" class="w-full p-3 bg-blue-600 text-white rounded">
Sign In
</button>
<p id="error" class="text-red-500 mt-2 hidden"></p>
</form>
</BaseLayout>
<script>
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../lib/firebase-client";
const form = document.getElementById("login-form") as HTMLFormElement;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const email = (document.getElementById("email") as HTMLInputElement).value;
const password = (document.getElementById("password") as HTMLInputElement).value;
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const token = await userCredential.user.getIdToken();
await fetch("/api/auth/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
window.location.href = "/dashboard";
} catch (error) {
const el = document.getElementById("error");
if (el) {
el.textContent = "Invalid email or password";
el.classList.remove("hidden");
}
}
});
</script>
Server-Side Session Verification
Verify Firebase tokens in your API routes:
// src/pages/api/auth/session.ts
import type { APIRoute } from "astro";
import { adminAuth } from "../../../lib/firebase-admin";
export const POST: APIRoute = async ({ request, cookies }) => {
const { token } = await request.json();
try {
const decodedToken = await adminAuth.verifyIdToken(token);
cookies.set("firebase-session", token, {
httpOnly: true,
secure: true,
path: "/",
maxAge: 60 * 60 * 24 * 7,
});
return new Response(JSON.stringify({ uid: decodedToken.uid }), {
status: 200,
});
} catch {
return new Response(JSON.stringify({ error: "Invalid token" }), {
status: 401,
});
}
};
Firestore Data Fetching
Read Firestore data in an Astro page at build time or request time:
---
// src/pages/products.astro
import { adminDb } from "../lib/firebase-admin";
import BaseLayout from "../layouts/BaseLayout.astro";
const snapshot = await adminDb.collection("products").orderBy("name").get();
const products = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
---
<BaseLayout title="Products">
<ul>
{products.map((product) => (
<li>
<h2>{product.name}</h2>
<p>${product.price}</p>
</li>
))}
</ul>
</BaseLayout>
Production Tips
Use the Admin SDK on the server only. The client SDK exposes your config (which is fine, it is designed for that), but the Admin SDK uses private credentials that must stay on the server. Never import
firebase-adminin client-side code.
Set up Firestore security rules. Default rules allow all access for 30 days. Lock them down before going to production. Use the Firebase console or deploy rules from a firestore.rules file.
Enable App Check. App Check verifies that requests to your Firebase backend come from your actual app, not from a script or bot. It adds a layer of protection against abuse.
Use composite indexes for complex queries. If you query Firestore with multiple where clauses or orderBy, you need composite indexes. Firebase will log a link to create them when a query fails.
Watch your read/write costs. Firestore charges per document read, write, and delete. Use pagination, cache responses, and avoid fetching entire collections on every page load.
Alternatives to Consider
- Supabase if you prefer PostgreSQL over NoSQL and want an open-source alternative with a similar feature set. Better for relational data.
- Neon or Turso if you just need a database without auth and storage bundled in. Lighter and cheaper at scale.
- Clerk if you only need authentication. It is simpler to set up than Firebase Auth and has pre-built UI components.
Wrapping Up
Firebase is a solid choice when you need multiple backend services under one roof. The client and admin SDKs cover both browser and server needs, and Astro's hybrid rendering lets you use each where it makes sense. Start with the free Spark plan and scale up as your app grows.
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.