Back to projects

School-wide web system

Early Bird Hub

I designed and built the production site solo so teachers across SITHS have one place to open the daily morning announcements.

Open Early Bird Hub ↗
Early Bird Hub bridge and sunrise artwork

Usage

200+ daily visits

Reach

SITHS classrooms

Implementation

Built solo

Publishing

Staff submissions + admin review

The hard part

Static Nuxt frontend with live Supabase data

The public Nuxt app deploys to Netlify, but approved announcements stay live through Supabase-backed API routes. A prebuild step also mirrors uploaded images from the school-hosted storage into the static deploy, so the site is not dependent on every image request reaching the school server.

Self-hosting

Self-hosted Supabase on school hardware

Instead of paying for a managed Supabase subscription, the database runs locally on a school computer. That avoids depending on a free hosted project that can pause after inactivity and keeps the recurring cost at zero.

Self-hosting created a second problem: a machine inside the school network is not normally reachable from the public internet. Opening inbound ports or changing the school router was not a realistic option, so the connection had to start from inside the network.

Diagram showing an SSH reverse tunnel connecting a server behind a firewall to a public SSH host

Reverse tunnel

SSH reverse-tunnel connection

The school computer opens an encrypted SSH connection to a public host. That outbound connection is allowed through the network, and the host maps a public endpoint back through the tunnel to the selected service running inside school.

Requests reach the public host, travel through the already-open tunnel, and arrive at the local server. The school router does not need a new inbound rule, and the database can remain on the school machine while the public app and management tools still reach it.

System model

System architecture

The public site, publishing tools, video feed, and school-hosted database are separate pieces joined through API routes.

Early Bird architecture

Input

Staff dashboard

Create, edit, or delete request

Gate

Admin review

Compare, approve, or reject

Data

Local Supabase

Auth, records, and image storage

External feed

YouTube playlist

Latest morning broadcast

Delivery

Nuxt + Netlify

Static interface with live API data

Output

SITHS classrooms

200+ visits on school days

The first whiteboard sketch of the Early Bird publishing architecture
The first architecture sketch. I worked through the publishing flow with peers, then implemented the production system solo.

First architecture pass

Initial publishing architecture

This was the first pass at separating the people publishing, the database storing changes, and the site classrooms actually open. The final build keeps records live and mirrors uploaded media during deployment.

Behind the homepage

Staff publishing and admin approval

Staff submit changes. The AV teacher reviews them. Only approved records reach the public page.

/admin

Early Bird

Announcement manager

Pending ApprovedCreate

Edit request

Robotics interest meeting

Pending

Before

Proposed

Approve Reject
Interface model based on the production moderation workflow.
01

Staff submit

Authenticated staff create announcements or request edits and deletions from a dedicated management dashboard.

02

Admin review

The AV teacher sees pending actions, including a before-and-after view for edits, then approves or rejects each change.

03

Publish live

Approved records appear through the public API and are grouped into today's announcements and earlier posts.

Moderation

Applying an approved edit

approve.post.ts
if (action.action_type === "edit" && action.old_id) {
  await supabase
    .from("daily_links")
    .update({
      name: action.name,
      description: action.description,
      url: action.url,
      img: action.img
    })
    .eq("id", action.old_id)

  await supabase
    .from("daily_links")
    .delete()
    .eq("id", actionId)
}

Build pipeline

Mirroring locally hosted images

prebuild.js
try {
  execSync("node scripts/copy-local-images.js", {
    stdio: "inherit"
  })

  console.log("Images bundled for deploy")
} catch (error) {
  console.log("Storage unavailable")
  console.log("Continuing build without copied images")
}

Actual use

School-day traffic

The repeating traffic spikes line up with school-day announcement use, reaching more than 200 visits per day across SITHS classrooms.

Early Bird Hub traffic chart showing school-day spikes above 200 visits

What the project taught me

01

A static frontend can still serve changing content. The important choice is deciding which parts belong in the deploy and which remain live behind an API.

02

The admin experience matters as much as the public page. The system only works daily if staff can contribute and the teacher can review changes without developer help.

03

Cost constraints can lead to real architecture decisions. Self-hosting removed the subscription cost but required a reliable path through the school network.

04

Deployment is part of the product. Authentication, database access, image storage, API routes, build scripts, tunneling, and hosting all had to work as one system.