A small Eleventy site that compiles a bespoke Bootstrap theme, fetches your recent Last.fm scrobbles at build time, and displays them in a responsive grid.
  • JavaScript 45.7%
  • Nunjucks 38.6%
  • SCSS 13.5%
  • Dockerfile 2.2%
Find a file
may 796924d5b5 Move workflow files to .forgejo directory
Renamed coolify-hourly.yml and vercel-hourly.yml from .github/workflows to .forgejo/workflows to update workflow configuration location.
2026-01-12 08:30:34 +01:00
.forgejo/workflows Move workflow files to .forgejo directory 2026-01-12 08:30:34 +01:00
lib Add top albums feature with caching and styling 2026-01-01 02:15:28 +01:00
src Update README and history configuration for improved Last.fm integration and site title handling 2026-01-01 09:09:20 +01:00
.dockerignore Add Docker support and update README for deployment 2026-01-01 09:44:09 +01:00
.eleventy.js initial 2025-12-31 23:38:11 +01:00
.env.example add history 2025-12-31 23:47:47 +01:00
.gitignore initial 2025-12-31 23:38:11 +01:00
Dockerfile Add Docker support and update README for deployment 2026-01-01 09:44:09 +01:00
LICENSE Add MIT License to the project 2026-01-01 09:15:27 +01:00
package-lock.json Update license from ISC to MIT in package-lock.json 2026-01-01 08:52:16 +01:00
package.json Update version to 0.1.0 in package.json 2026-01-01 11:42:10 +01:00
README.md Add hourly rebuild GitHub Actions for Vercel and Coolify 2026-01-01 10:19:08 +01:00
vercel.json.example Add hourly rebuild GitHub Actions for Vercel and Coolify 2026-01-01 10:19:08 +01:00

Musiclisten · Eleventy × Last.fm

A small Eleventy site that compiles a bespoke Bootstrap theme, fetches your recent Last.fm scrobbles at build time, and displays them in a responsive grid. Responses are cached locally so builds stay fast and API limits remain happy.

Requirements

  • Node.js 18+ (relies on the global fetch implementation)
  • A Last.fm account and API key

Getting started

  1. Install dependencies
    npm install
    
  2. Copy the environment template and fill in your secrets
    cp .env.example .env
    
  3. Build once (or run the dev server) to generate the site
    npm run build
    # or
    npm run serve
    

Docker / Coolify deployment

Use the provided Dockerfile to build a static image that serves the dist/ folder with NGINX. Because Eleventy fetches data during the build, you must pass the required Last.fm variables as build arguments (Coolify exposes a UI for this).

docker build \
   --build-arg LASTFM_API_KEY=yourkey \
   --build-arg LASTFM_USERNAME=yourname \
   --build-arg LASTFM_CACHE_MINUTES=60 \
   --build-arg LASTFM_HISTORY_PAGES=5 \
   --build-arg SITE_TITLE="Musiclisten" \
   -t musiclisten .

docker run -p 8080:80 musiclisten

Rebuild the image whenever you want to refresh cached data. If you prefer not to bake secrets into the image, use a BuildKit secret or trigger the build inside Coolify where the args remain server-side.

Hourly rebuilds on Vercel (GitHub Actions)

Vercels cron feature is unavailable on the Hobby plan, so the repo ships with .github/workflows/vercel-hourly.yml. It triggers every hour (UTC) or on demand and simply calls a Vercel Deploy Hook.

  1. In your Vercel project, create a Deploy Hook (Settings → Git → Deploy Hooks). Copy the generated URL.
  2. In GitHub, go to Settings → Secrets and variables → Actions for this repository and add a new secret named VERCEL_DEPLOY_HOOK with that URL.
  3. The workflow will now run hourly, hit the hook via curl, and Vercel will rebuild the project using the environment variables defined in the Vercel dashboard.

If you need a different cadence, adjust the cron expression in vercel-hourly.yml. The workflow also includes workflow_dispatch, so you can trigger it manually from the Actions tab whenever you want a fresh deploy outside the hourly window.

Hourly rebuilds on Coolify (GitHub Actions)

Coolify deploy hooks require an authenticated request, so .github/workflows/coolify-hourly.yml makes the same hourly call but adds a Bearer token header.

  1. In Coolify, open the service → Deployments → Deploy Hooks, generate a hook URL, and note the accompanying API token (or create a PAT with deploy permission).
  2. In GitHub Actions secrets, add COOLIFY_DEPLOY_HOOK (the hook URL) and COOLIFY_DEPLOY_TOKEN (the bearer token).
  3. The workflow will run on the same 0 * * * * schedule and POST to the hook with Authorization: Bearer <token>. Adjust the cron or trigger manually via workflow_dispatch as needed.

Environment variables

Key Description
SITE_TITLE Title of your page
LASTFM_API_KEY API key you receive from the Last.fm developer console.
LASTFM_USERNAME The Last.fm username (profile slug) whose listening history should be fetched.
LASTFM_CACHE_MINUTES Optional override for the cache TTL. Defaults to 15 minutes.
LASTFM_HISTORY_PAGES Number of Last.fm pages (200 tracks each) to fetch for the history archive. Defaults to 1.

Getting your Last.fm API key & username

  1. Sign in (or register) at Last.fm.
  2. Go to the API account page and create an application.
  3. Once the app is approved instantly, copy the API Key value into LASTFM_API_KEY inside .env.
  4. Your LASTFM_USERNAME is simply your profile URL slug — e.g. https://www.last.fm/user/<username>.

Keep the .env file out of version control (already covered via .gitignore).

Scripts

Command Purpose
npm run build:css Compile src/styles/main.scss (Bootstrap + theme overrides) to src/assets/css/main.css.
npm run watch:css Watch Sass files for changes during local development. Run alongside npm run serve.
npm run build:site Run Eleventy without rebuilding CSS.
npm run build Compile CSS then run Eleventy to produce dist/.
npm run serve Compile CSS once and start Eleventys dev server at http://localhost:8080.
npm run clean Remove dist/ and .cache/.

Data flow & caching

  • _data/lastfm.js runs on every Eleventy build, loads .env, and checks .cache/lastfm.json for the last successful fetch.
  • If the cache is fresher than LASTFM_CACHE_MINUTES, the cached payload is used to avoid another HTTP request.
  • When the cache is stale (or missing) the build fetches user.getrecenttracks from the Last.fm REST API, normalizes the response, saves it to .cache/lastfm.json, and exposes it to templates.
  • Any API failures fall back to the last good cache (if present) and emit a warning banner in the UI.
  • _data/history.js performs a multi-page fetch (up to LASTFM_HISTORY_PAGES × 200 tracks) to build the /history/ archive, storing the expanded payload in .cache/history.json with the same TTL behavior.
  • _data/topAlbums.js hits user.gettopalbums with a 1month period and caches the top 10 albums in .cache/top-albums.json so the leaderboard page stays responsive.

History page

  • src/history.njk renders a table-style archive that can include up to 1,000 tracks out of the box (5 pages × 200). Increase LASTFM_HISTORY_PAGES if you want to go deeper, keeping in mind the trade-off between build time and API rate limits.
  • The navigation in layouts/base.njk now links to the new page so you can jump between the live dashboard and the archive.

Top albums page

  • src/top-albums.njk showcases the 10 most played albums from the previous month (per Last.fms 1month window) with artwork, play counts, and profile links.
  • You can tweak the TTL via LASTFM_CACHE_MINUTES if youd like to refresh the leaderboard more or less frequently.

Custom Bootstrap build

  • src/styles/main.scss imports Bootstraps Sass entrypoint with custom color, typography, and layout tweaks.
  • Output CSS lives in src/assets/css/main.css so Eleventy can pass it through directly to dist/assets/css/main.css.
  • Feel free to extend the theme by editing the Sass file or adding additional partials. Run npm run watch:css to see changes instantly.

Troubleshooting

  • “Missing Last.fm configuration” alert: Ensure .env contains both LASTFM_API_KEY and LASTFM_USERNAME, then rebuild.
  • API errors or rate limiting: Increase LASTFM_CACHE_MINUTES or delete .cache/lastfm.json to force a refetch after resolving issues.
  • Stale styling: Delete src/assets/css/main.css and rerun npm run build:css if the compiled file falls out of sync.

License

MIT © 2025