Modern, mobile-first band website built with Next.js (App Router), React, TypeScript, and Tailwind CSS. Band and member content live in local JSON files, while shows and gallery media are stored in SQLite. Docker is ready for Raspberry Pi 5.
npm install
npm run devnpm run build
npm startdocker compose up --buildSite runs at http://127.0.0.1:3000
Edit the JSON files under data/:
data/band.json- name, location, socials, streamingdata/members.json- band member cards
Shows and gallery media are managed through /admin, not JSON.
Streaming buttons are driven by data/band.json → streaming.
- To show a clickable button, set the value to a URL string:
{
"streaming": {
"spotify": "https://open.spotify.com/artist/REAL_ID",
"appleMusic": "https://music.apple.com/artist/REAL_ID"
}
}When Spotify is set to a supported Spotify URL (artist, album, track, playlist, show, or episode), the home page also renders an embedded Spotify preview player under Listen.
- To show a non-clickable "Coming Soon" label, set the value to a URL string
that equals
coming soon(case-insensitive), or to:
{
"streaming": {
"spotify": { "status": "coming-soon" },
"appleMusic": { "status": "coming-soon" }
}
}Videos on /video and photos on /video/photos are loaded from admin gallery metadata in SQLite. Uploaded gallery files live in runtime storage. Use /admin → Videos to add, edit, delete, or sync video cards, and /admin → Images to upload, delete, or sync gallery photos.
Place other images in public/images/ and reference them by absolute path in JSON, e.g.
/images/members/avery.svg/images/logos/sweetside-logo.png
Video thumbnails are uploaded through /admin and served from runtime storage through /video-thumbnails/:fileName. Thumbnails must be .jpg, .jpeg, or .png files up to 250 KB.
Copy .env.example to .env or .env.local.
Available variables:
SITE_URL- canonical site URL used for metadata, sitemap, and robotsADMIN_PASSWORD_HASH- salted scrypt hash for the fixedadminusername. Escape both$delimiters as\$when saving it in.envor.env.local.ADMIN_SESSION_SECRET- random secret for signing the admin session cookieSHOWS_DB_PATH- optional override for the SQLite database path
Shows load through lib/shows-db.ts.
- Local default database path is
data/shows.sqlite. - Docker stores the database in a named volume mounted at
/app/storage/shows.sqlite. - Public pages still read through
lib/content.ts, so the UI contracts stay the same. - Show IDs use the immutable
ss-xxxxxxxxformat, with eight random lowercase hex characters. Existing databases migrate automatically on first startup. - Uploaded posters are stored in runtime storage.
- Local poster storage defaults to
storage/posters/. - Docker poster storage lives beside the database inside the mounted
/app/storagevolume. - Posters are served through
/posters/:fileName. - Uploaded gallery images are stored in runtime storage.
- Local gallery image storage defaults to
storage/gallery-images/. - Docker gallery image storage lives beside the database inside the mounted
/app/storagevolume. - Original gallery JPEGs and generated WebP previews are served through
/gallery-images/:fileNamewith immutable cache headers. - Uploads generate a WebP preview up to 1,200 px wide at 80% quality; both files live in the same gallery image storage directory and Docker volume.
- Uploaded video thumbnails are stored in runtime storage.
- Local video thumbnail storage defaults to
storage/video-thumbnails/. - Docker video thumbnail storage lives beside the database inside the mounted
/app/storagevolume. - Video thumbnails are served through
/video-thumbnails/:fileNamewith immutable cache headers. - Upcoming shows may also store an optional
tickets_urlvalue in SQLite for the public Tickets button.
The admin UI lives at /admin.
Use the admin UI for show management. JSON editing no longer applies to shows.
The admin dashboard lets you:
- review upcoming and past shows
- edit any show fields
- add or clear optional upcoming-only ticket links
- add and delete shows
- upload PNG posters only
- upload posters up to 5 MB each, with a 20 MB combined upload cap per sync
- upload and delete photo gallery images
- upload photo gallery images as
.jpgor.jpegfiles up to 1 MB each - add, edit, delete, and sync video gallery cards
- upload video thumbnails as
.jpg,.jpeg, or.pngfiles up to 250 KB each - sync show draft state and revalidate
/and/shows - sync video draft state and revalidate
/video
Admin API protection:
- login and sync endpoints use an in-memory process-wide rate limiter
- logout clears the admin session cookie server-side and the client navigates directly to
/admin/login - sync validates poster extension, PNG file signature, upload size limits, and show writes in the same request
- gallery image uploads validate extension, JPEG file signature, and upload size before storing files on disk and metadata in SQLite
- opening the admin dashboard or using Images → Save changes idempotently backfills missing WebP previews for existing photos
- video sync validates YouTube links, thumbnail extension, image file signature, and upload size before storing files on disk and metadata in SQLite