From 74b6fb29bc44d31b698e00f68c25ebb1bbec5466 Mon Sep 17 00:00:00 2001 From: theoneand33 Date: Tue, 15 Sep 2026 12:04:37 +1200 Subject: [PATCH 1/2] fix(games): handle unknown slugs with 404 and guard stale entries - Return 404 for unknown game slugs instead of crashing - Filter stale slugs from home and More Games lists - Fix IndexNow to read sitemap-index children; drop dead slash redirects --- astro.config.mjs | 8 +------- scripts/indexnow.mjs | 15 ++++++++++----- src/components/flash.astro | 7 +++++++ src/layout/layout.astro | 5 +++-- src/pages/games/[slug].astro | 7 +++++-- src/pages/index.astro | 4 ++-- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 5705a66..d4a962c 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -10,18 +10,12 @@ export default defineConfig({ site: "https://unblocked-games.vercel.app", trailingSlash: "never", // Slug variants that Google still serves impressions for — consolidate to canonical slugs. + // ponytail: trailingSlash "never" already canonicalizes trailing slashes, so no slash variants here redirects: { "/games/happywheels": "/games/happy-wheels", "/games/run3": "/games/run-3", "/games/gun-mayhem2": "/games/gun-mayhem-2", "/games/geography-game": "/games/geography-game-usa", - "/games/tetris/": "/games/tetris", - "/games/clicker-heroes/": "/games/clicker-heroes", - "/games/fleeing-the-complex/": "/games/fleeing-the-complex", - "/games/breaking-the-bank/": "/games/breaking-the-bank", - "/games/hobo-5-space-brawls/": "/games/hobo-5-space-brawls", - "/games/super-mario-flash/": "/games/super-mario-flash", - "/games/strike-force-heroes/": "/games/strike-force-heroes", }, integrations: [ sitemap({ diff --git a/scripts/indexnow.mjs b/scripts/indexnow.mjs index 1139650..06cc26d 100644 --- a/scripts/indexnow.mjs +++ b/scripts/indexnow.mjs @@ -12,14 +12,19 @@ if (!key) { } const host = "unblocked-games.vercel.app"; +const locs = (xml) => + [...xml.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1]); let urls = []; try { - const xml = readFileSync("dist/sitemap.xml", "utf8"); - urls = [...xml.matchAll(/([^<]+)<\/loc>/g)] - .map((m) => m[1]) - .slice(0, 10000); + // ponytail: astro emits sitemap-index.xml + sitemap-N.xml, never sitemap.xml + const index = readFileSync("dist/sitemap-index.xml", "utf8"); + const children = locs(index).map((u) => u.split("/").pop()); + for (const file of children) { + urls.push(...locs(readFileSync(`dist/${file}`, "utf8"))); + } + urls = urls.slice(0, 10000); } catch { - console.log("IndexNow: dist/sitemap.xml not found, run build first."); + console.log("IndexNow: dist/sitemap-index.xml not found, run build first."); process.exit(1); } diff --git a/src/components/flash.astro b/src/components/flash.astro index 1ccd4e6..bf4d170 100644 --- a/src/components/flash.astro +++ b/src/components/flash.astro @@ -83,6 +83,9 @@ const { gamePath } = Astro.props; var headerOffset = isFs ? 56 : 120; var maxW = isFs ? vw : Math.min(vw - pad * 2, 1040); var maxH = vh - headerOffset - pad * 2; + // ponytail: clamp so short viewports never yield negative px sizes + maxW = Math.max(200, maxW); + maxH = Math.max(200, maxH); var w = maxW; var h = w / ASPECT_RATIO; if (h > maxH) { @@ -114,6 +117,10 @@ const { gamePath } = Astro.props; function ready() { if (destroyed) return; // ponytail: fail fast on script 404/offline, keep polling for slow networks + if (!GAME_PATH) { + showError("Game not available."); + return; + } if (window.__ruffleFailed) { showError("Failed to load Flash player. Please refresh the page."); return; diff --git a/src/layout/layout.astro b/src/layout/layout.astro index d72c775..7bf3eed 100644 --- a/src/layout/layout.astro +++ b/src/layout/layout.astro @@ -48,9 +48,10 @@ const pageUrl = slug ? `${siteUrl}/games/${slug}` : siteUrl; // More Games cross-linking — same genre first, then fill from popular // ponytail: two passes over defaultGames, no new data structures +// ponytail: drop unknown slugs so one stale entry can't crash every page const moreGames = (() => { - if (!slug || !seo) return defaultGames.slice(0, MORE_GAMES_COUNT); - const rest = defaultGames.filter((s) => s !== slug); + if (!slug || !seo) return defaultGames.filter((s) => gamesMap[s]).slice(0, MORE_GAMES_COUNT); + const rest = defaultGames.filter((s) => s !== slug && gamesMap[s]); return [ ...rest.filter((s) => gamesMap[s].genre === seo.genre), ...rest.filter((s) => gamesMap[s].genre !== seo.genre), diff --git a/src/pages/games/[slug].astro b/src/pages/games/[slug].astro index cdf444e..9f9b841 100644 --- a/src/pages/games/[slug].astro +++ b/src/pages/games/[slug].astro @@ -8,8 +8,11 @@ export async function getStaticPaths() { return Object.keys(gamesMap).map((slug) => ({ params: { slug } })); } -const { slug } = Astro.params!; -const seo = gamesMap[slug]; +const { slug } = Astro.params; +const seo = slug ? gamesMap[slug] : undefined; +if (!seo) { + return new Response(null, { status: 404, statusText: "Not Found" }); +} --- diff --git a/src/pages/index.astro b/src/pages/index.astro index e30ac8c..7e6e120 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -6,7 +6,7 @@ import { gamesMap, defaultGames, POPULAR_COUNT } from "../data/game-seo"; const pageTitle = "Superfun Games - Unblocked Games for School Chromebook | No Flash Required (2026)"; -const popularGames = defaultGames.slice(0, POPULAR_COUNT); +const popularGames = defaultGames.filter((s) => gamesMap[s]).slice(0, POPULAR_COUNT); --- @@ -97,7 +97,7 @@ const popularGames = defaultGames.slice(0, POPULAR_COUNT);

All unblocked games

{ - defaultGames.map((slug) => { + defaultGames.filter((s) => gamesMap[s]).map((slug) => { const seo = gamesMap[slug]; return ( Date: Tue, 15 Sep 2026 12:08:24 +1200 Subject: [PATCH 2/2] agent: address PR review feedback (#88) Filter stale slugs in define:vars lists (random button, recently-played); restore trailing-slash redirects. --- astro.config.mjs | 8 +++++++- src/layout/layout.astro | 9 +++++---- src/pages/index.astro | 7 ++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index d4a962c..5705a66 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -10,12 +10,18 @@ export default defineConfig({ site: "https://unblocked-games.vercel.app", trailingSlash: "never", // Slug variants that Google still serves impressions for — consolidate to canonical slugs. - // ponytail: trailingSlash "never" already canonicalizes trailing slashes, so no slash variants here redirects: { "/games/happywheels": "/games/happy-wheels", "/games/run3": "/games/run-3", "/games/gun-mayhem2": "/games/gun-mayhem-2", "/games/geography-game": "/games/geography-game-usa", + "/games/tetris/": "/games/tetris", + "/games/clicker-heroes/": "/games/clicker-heroes", + "/games/fleeing-the-complex/": "/games/fleeing-the-complex", + "/games/breaking-the-bank/": "/games/breaking-the-bank", + "/games/hobo-5-space-brawls/": "/games/hobo-5-space-brawls", + "/games/super-mario-flash/": "/games/super-mario-flash", + "/games/strike-force-heroes/": "/games/strike-force-heroes", }, integrations: [ sitemap({ diff --git a/src/layout/layout.astro b/src/layout/layout.astro index 7bf3eed..3a65c54 100644 --- a/src/layout/layout.astro +++ b/src/layout/layout.astro @@ -47,11 +47,12 @@ const gameYear = seo?.year || ""; const pageUrl = slug ? `${siteUrl}/games/${slug}` : siteUrl; // More Games cross-linking — same genre first, then fill from popular -// ponytail: two passes over defaultGames, no new data structures +// ponytail: two passes over knownGames, no new data structures // ponytail: drop unknown slugs so one stale entry can't crash every page +const knownGames = defaultGames.filter((s) => gamesMap[s]); const moreGames = (() => { - if (!slug || !seo) return defaultGames.filter((s) => gamesMap[s]).slice(0, MORE_GAMES_COUNT); - const rest = defaultGames.filter((s) => s !== slug && gamesMap[s]); + if (!slug || !seo) return knownGames.slice(0, MORE_GAMES_COUNT); + const rest = knownGames.filter((s) => s !== slug); return [ ...rest.filter((s) => gamesMap[s].genre === seo.genre), ...rest.filter((s) => gamesMap[s].genre !== seo.genre), @@ -347,7 +348,7 @@ const moreGames = (() => { sync(); })(); -