DEV Community

Cover image for How I Built a "Serverless" Student Portfolio Platform for $0 Where GitHub IS the Database
Humayan Al Rosid
Humayan Al Rosid Subscriber

Posted on

How I Built a "Serverless" Student Portfolio Platform for $0 Where GitHub IS the Database

The Problem

Every student needs a portfolio link for their resume. But let's be honest:

  • Hosting costs money. (Even $5/mo adds up for a student).
  • Configuring DNS is annoying. (CNAME records, propagation, SSL certificates...).
  • Maintenance is a pain.

I wanted to build a platform that gives every student a professional yourname.isat.college subdomain instantly, without me paying for a database or managing a complex backend.

So I built isat.college, a Student Identity Platform where "User Signup" is literally a GitHub Pull Request.

Here is the breakdown of how I built a production-ready SaaS architecture for exactly $0 monthly cost.

The Architecture: "Open Data, Private Engine"

Instead of a traditional SaaS where I own the code and the database, I split it up:

  • The Registry (Open Source): A public GitHub repository containing user profiles as JSON files. This acts as the "Database."
  • The Engine (Proprietary): A private Astro + Cloudflare application that fetches that data and renders the portfolios. This hybrid model is powerful. It allows me to maintain quality control on the rendering engine while giving students 100% ownership of their data via the MIT-licensed registry.

The Stack

  • Database: GitHub Repository (JSON files).
  • Frontend: Astro + React (Client-Side Rendering).
  • Routing: Cloudflare Workers (Wildcard Subdomains).
  • Hosting: Cloudflare Pages.

How it Works (The Magic) 🪄

Most Static Site Generators trigger a build every time data changes. If I had 1,000 users, I didn't want 1,000 builds queueing up every time someone fixed a typo.

So I ditched the "Build Step" entirely.

1. The "Wildcard" Routing (Cloudflare Workers)

I set up a Wildcard DNS record (*.isat.college) pointing to a single Cloudflare Worker. This worker acts as a reverse proxy.

Whether you visit alice.isat.college or bob.isat.college, the Worker intercepts the request and serves the exact same static Astro app.

Here is the actual Worker code that powers the routing:

export default {
    async fetch(request) {
        const url = new URL(request.url);

        // The "House" where the main content lives (Cloudflare Pages)
        const TARGET_HOST = 'isat-college.pages.dev';

        // Only proxy if it's NOT the main domain (optional safety check)
        // This allows the root domain to be served directly if DNS is set that way,
        // but for wildcard routes, this worker will trigger.

        // Change the hostname to point to the Pages backend
        url.hostname = TARGET_HOST;

        // Create a new request with the updated URL
        // We preserve the original method, headers, and body
        const proxyRequest = new Request(url, request);

        // Forward the request to Cloudflare Pages
        // The browser still sees "alice.isat.college", 
        // but we fetch content from the main app invisibly.
        return fetch(proxyRequest); 
    }
};
Enter fullscreen mode Exit fullscreen mode

2. The Client-Side Hydration

Once the page loads in the browser, the React frontend takes over. It checks the URL bar, extracts the subdomain, and fetches the raw data from the Open Registry.

Here is the ProfileLoader component that handles this logic:

// Inside ProfileLoader.tsx
useEffect(() => {
    let hostname = window.location.hostname;
    // ... logic to extract 'alice' from 'alice.isat.college' ...

    // Fetch profile data directly from the deployed site's public folder
    // or GitHub Raw URL
    const fetchProfile = async () => {
      try {
        // Cache Busting Strategy:
        // 1. Add unique timestamp to URL to bypass CDN cache
        // 2. Use { cache: 'no-store' } to bypass browser cache
        const baseUrl = SITE_CONFIG.getProfileUrlLocal(username);
        const url = `${baseUrl}?v=${Date.now()}`;

        console.log('Fetching profile from:', url);

        const response = await fetch(url, { cache: 'no-store' });

        if (!response.ok) {
          if (response.status === 404) throw new Error('Profile not found');
          throw new Error('Failed to load profile');
        }

        const data = await response.json();
        setProfileData(data);
      } catch (err: any) {
        setError(err.message || 'An error occurred');
      }
    };

    fetchProfile();
}, []);
Enter fullscreen mode Exit fullscreen mode

If the file exists, it renders the portfolio instantly using the selected theme. If not, it shows a 404.

The Cost Breakdown 💸

This is the best part. Because I am leaning on the "Edge" and GitHub's infrastructure, my running costs are non-existent.

  • Hosting (Cloudflare Pages): Free Tier.
  • Database (GitHub): Free.
  • SSL Certificates: Free (Cloudflare Auto-SSL).
  • Domain Name: ~$3/year (For 1st year).

Total Monthly Cost: $0.

Why GitHub as a Database?

I often get asked: "Why not use Firebase or Cloudflare D1?"

Technically, those would work. But by using GitHub:

  • I didn't have to build an Admin Dashboard.
  • I didn't have to build Auth/Login.
  • Transparency: Students can see exactly where their data lives.

It forces students to use Git, which is a valuable skill for their resume anyway.

The domain is also a fun wordplay:
alice.isat.college ➡️ "Alice is at College" 🎓

Claim Your Identity

The Registry is open for contributions. If you are a student, you can claim your username by submitting a simple Pull Request.

Let me know what you think of this architecture!

Top comments (0)