Astro with Firebase

Published on

Astro is probably my favorite static site generator. Here’s why:

  • Uses npm as a package manager
  • Uses JavaScript only where you explicitly ask for it, otherwise just HTML + CSS
  • Supports multiple content collections (posts, projects, services, products etc.)
  • Multi-framework support: use React, Vue, Svelte, or vanilla JS components together
  • File-based routing: Pages map directly to your file structure
  • Happy Astro users include Microsoft and Firebase

Besides building my portfolio website with Astro, I’ve also spent some time on building an application that uses both client-side rendering (CSR) for an admin dashboard, and server-side rendering (SSR) for publicly available profiles.

Here are my takeaways.

Getting started with Astro is super easy

npm create astro@latest my-project
cd my-project
npm run dev
# Opens localhost:4321

That’s it. You’ll have a working Astro site in under 2 minutes. The npm create magic will download the latest version of create-astro package temporarily, run it immediately, then clean up after itself.

Create custom collections

In content.config.ts you can define custom collections.

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const projects = defineCollection({
	// Load Markdown and MDX files in the `src/content/blog/` directory.
	loader: glob({ base: './src/content/projects', pattern: '**/*.{md,mdx}' }),
	// Type-check frontmatter using a schema
	schema: ({ image }) =>
		z.object({
			title: z.string(),
			description: z.string(),
			link: z.string().url().optional(),
			image: z.string(),
			labels: z.array(z.string()), // Array of strings
			published: z.boolean().optional(),

		}),
});

export const collections = { projects };

Then you can create and edit your collection items in src/content/{collection}. To create views and template files for these items you can create

  • /pages/{collection}/index.astro for main page of the collection
  • /pages/{collection}/[...slug].astro for items

Collections are ideal for creating entities that share characteristics, like projects, case studies, services, products etc.

Firebase Hosting setup

Setting up Firebase Hosting couldn’t be easier either.

npm install -g firebase-tools
firebase login
cd my-project
firebase init hosting
npm run build
firebase deploy

Server side rendering with Firebase

Let’s say you are building a hybrid application that uses both client-side rendering (CSR) and server side rendering (SSR). This is the case if your app has both a private admin area and a public facing profile area that is to be indexed by search engines.

To achieve this, you want to create public profile pages and populate your Astro page with Firestore data.

You can do this by redirecting the request on a given URL to a Firebase Function, that will query Firestore, generate an HTML page based on an Astro template and return the result.

Firebase Hosting rewrite

In firebase.json add the following:

{
	"hosting": {
		"rewrites": [
			{      
				"source": "/@**",
      		  	"function": "getProfilePage"
      
			}
		]
	}
}

This rewrite will catch all URLS that look like your-website.com/@anthony and redirect the request to the getProfilePage Firebase Function.

Firebase Function

Inside your function, you can query your Firestore, read your HTML template, and inject the data using placeholders.

const coursePagesRef = firestore.collection('coursePages');
const publicCoursePagesSnapshot = await coursePagesRef
	.where('userId', '==', userId)
	.where('isPublished', '==', true)
	.get();

let publicCoursePagesHtml = '';
if (!publicCoursePagesSnapshot.empty) {
	publicCoursePagesSnapshot.forEach(doc => {
	const course = doc.data();
	publicCoursePagesHtml += `<li><a href="/${course.coursePageId}" target="_blank">${course.courseTitle}</a></li>`;
	});
	publicCoursePagesHtml = `<ul>${publicCoursePagesHtml}</ul>`;
} else {
	publicCoursePagesHtml = '<p>No public courses yet.</p>';
}

// Read the profile.html template
const templatePath = path.join(process.cwd(), 'profile.html');
let profileHtml = fs.readFileSync(templatePath, 'utf8');

// Replace placeholders with actual data
profileHtml = profileHtml.replace('<!-- PROFILE_PAGE_ID -->', profilePageId);
profileHtml = profileHtml.replace('<!-- DISPLAY_NAME -->', displayName);
profileHtml = profileHtml.replace('<!-- PUBLIC_COURSE_PAGES -->', publicCoursePagesHtml);

// Send response
return res.status(200).send(profileHtml);

This way you have successfully integrated Firestore into your Astro page.

Senior Full Stack Web Developer with 10+ years of experience

I'm Gábor, a cheerful and enthusiastic freelance web developer who builds and responds fast. Want to work together? Drop me a line at gabor@gaborpinter.com.