home page updated
This commit is contained in:
102
site/content/cache/content.json
vendored
102
site/content/cache/content.json
vendored
File diff suppressed because one or more lines are too long
148
site/public/sw.js
Normal file
148
site/public/sw.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/* Service Worker: lightweight caching for a static Astro site.
|
||||
- Navigations: network-first (avoid stale HTML indefinitely)
|
||||
- Site shell: pre-cache on install
|
||||
- Images: cache-first with bounded eviction
|
||||
*/
|
||||
|
||||
// Bump this value on deploy to invalidate caches.
|
||||
const CACHE_VERSION = "v1";
|
||||
|
||||
const CACHE_SHELL = `shell-${CACHE_VERSION}`;
|
||||
const CACHE_PAGES = `pages-${CACHE_VERSION}`;
|
||||
const CACHE_MEDIA = `media-${CACHE_VERSION}`;
|
||||
|
||||
const SHELL_ASSETS = ["/", "/styles/global.css", "/favicon.svg", "/favicon.ico", "/robots.txt"];
|
||||
|
||||
// Keep media cache bounded so we don't grow indefinitely.
|
||||
const MAX_MEDIA_ENTRIES = 80;
|
||||
|
||||
const isGet = (request) => request && request.method === "GET";
|
||||
|
||||
const isNavigationRequest = (request) =>
|
||||
request.mode === "navigate" || request.destination === "document";
|
||||
|
||||
const isImageRequest = (request, url) => {
|
||||
if (request.destination === "image") return true;
|
||||
const p = url.pathname.toLowerCase();
|
||||
return (
|
||||
p.endsWith(".png") ||
|
||||
p.endsWith(".jpg") ||
|
||||
p.endsWith(".jpeg") ||
|
||||
p.endsWith(".webp") ||
|
||||
p.endsWith(".gif") ||
|
||||
p.endsWith(".avif") ||
|
||||
p.endsWith(".svg")
|
||||
);
|
||||
};
|
||||
|
||||
async function trimCache(cacheName, maxEntries) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const keys = await cache.keys();
|
||||
const extra = keys.length - maxEntries;
|
||||
if (extra <= 0) return;
|
||||
// Cache keys are returned in insertion order in practice; delete the oldest.
|
||||
for (let i = 0; i < extra; i += 1) {
|
||||
await cache.delete(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
async function cachePutSafe(cacheName, request, response) {
|
||||
// Only cache successful or opaque responses. Avoid caching 404/500 HTML.
|
||||
if (!response) return;
|
||||
if (response.type !== "opaque" && !response.ok) return;
|
||||
const cache = await caches.open(cacheName);
|
||||
await cache.put(request, response);
|
||||
}
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const cache = await caches.open(CACHE_SHELL);
|
||||
await cache.addAll(SHELL_ASSETS);
|
||||
// Activate new worker ASAP to pick up new caching rules.
|
||||
await self.skipWaiting();
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const keep = new Set([CACHE_SHELL, CACHE_PAGES, CACHE_MEDIA]);
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map((k) => (keep.has(k) ? Promise.resolve() : caches.delete(k))));
|
||||
await self.clients.claim();
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const { request } = event;
|
||||
if (!isGet(request)) return;
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Only handle http(s).
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") return;
|
||||
|
||||
// Network-first for navigations (HTML documents). Cache as fallback only.
|
||||
if (isNavigationRequest(request)) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
try {
|
||||
const fresh = await fetch(request);
|
||||
// Cache a clone so we can serve it when offline.
|
||||
await cachePutSafe(CACHE_PAGES, request, fresh.clone());
|
||||
return fresh;
|
||||
} catch {
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
// Fallback: try cached homepage shell.
|
||||
const home = await caches.match("/");
|
||||
if (home) return home;
|
||||
throw new Error("No cached navigation fallback.");
|
||||
}
|
||||
})(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache-first for images/media with bounded cache size.
|
||||
if (isImageRequest(request, url)) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
const cached = await caches.match(request);
|
||||
if (cached) return cached;
|
||||
|
||||
const res = await fetch(request);
|
||||
await cachePutSafe(CACHE_MEDIA, request, res.clone());
|
||||
await trimCache(CACHE_MEDIA, MAX_MEDIA_ENTRIES);
|
||||
return res;
|
||||
})(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stale-while-revalidate for styles/scripts/fonts from same-origin.
|
||||
if (
|
||||
url.origin === self.location.origin &&
|
||||
(request.destination === "style" ||
|
||||
request.destination === "script" ||
|
||||
request.destination === "font")
|
||||
) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
const cached = await caches.match(request);
|
||||
const networkPromise = fetch(request)
|
||||
.then(async (res) => {
|
||||
await cachePutSafe(CACHE_SHELL, request, res.clone());
|
||||
return res;
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return cached || (await networkPromise) || fetch(request);
|
||||
})(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,6 +55,24 @@ const canonicalUrl = `${siteUrl}${canonicalPath.startsWith("/") ? canonicalPath
|
||||
<script async defer data-website-id={cfg.umami.websiteId} src={cfg.umami.scriptUrl} />
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
// Register SW only in production builds (Astro sets import.meta.env.PROD at build time).
|
||||
import.meta.env.PROD ? (
|
||||
<script is:inline>
|
||||
{`
|
||||
if ("serviceWorker" in navigator) {
|
||||
// SW requires HTTPS (or localhost). In prod we expect HTTPS.
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(() => {
|
||||
// noop: SW is progressive enhancement
|
||||
});
|
||||
});
|
||||
}
|
||||
`}
|
||||
</script>
|
||||
) : null
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main-content">Skip to content</a>
|
||||
@@ -108,15 +126,7 @@ const canonicalUrl = `${siteUrl}${canonicalPath.startsWith("/") ? canonicalPath
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href="/about"
|
||||
data-umami-event="click"
|
||||
data-umami-event-target_id="nav.about"
|
||||
data-umami-event-placement="nav"
|
||||
data-umami-event-target_url="/about"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import CtaLink from "../components/CtaLink.astro";
|
||||
import { LINKS } from "../lib/links";
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="About | SanthoshJ"
|
||||
description="About SanthoshJ and where to follow."
|
||||
canonicalPath="/about"
|
||||
>
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h2>About</h2>
|
||||
<span class="muted">Tech, streaming, movies, travel</span>
|
||||
</div>
|
||||
<div class="empty">
|
||||
<p style="margin-top: 0;">
|
||||
This is a lightweight site that aggregates my content so it can be discovered via search and
|
||||
shared cleanly.
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<CtaLink platform="youtube" placement="about" url={LINKS.youtubeChannel} label="YouTube" />
|
||||
<CtaLink
|
||||
platform="instagram"
|
||||
placement="about"
|
||||
url={LINKS.instagramProfile}
|
||||
label="Instagram"
|
||||
/>
|
||||
<CtaLink platform="podcast" placement="about" url={LINKS.podcast} label="Podcast" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
@@ -26,22 +26,21 @@ const pods = podcastEpisodes(cache)
|
||||
|
||||
<BaseLayout
|
||||
title="SanthoshJ | Tech, streaming, movies, travel"
|
||||
description="A fast, SEO-first home for videos, movie posts, and the Irregular Mind podcast."
|
||||
description="SanthoshJ shares tech, gaming streams, movie recommendations, and travel stories—plus the Irregular Mind podcast. Explore the newest videos and episodes."
|
||||
canonicalPath="/"
|
||||
>
|
||||
<section class="hero">
|
||||
<div>
|
||||
<h1>Fast content. Clear next actions.</h1>
|
||||
<h1>Tech, gaming, movies & travel — videos + podcast by SanthoshJ</h1>
|
||||
<p>
|
||||
I post about technology, game streaming, movies, and travel. This site collects the best of
|
||||
it and points you to the platform you prefer.
|
||||
Quick takes and long-form stories: tech explainers, game streaming highlights, movie recommendations, and travel notes—curated in one place.
|
||||
</p>
|
||||
<div class="cta-row">
|
||||
<CtaLink
|
||||
platform="youtube"
|
||||
placement="hero"
|
||||
url={LINKS.youtubeChannel}
|
||||
label="Subscribe on YouTube"
|
||||
label="Watch on YouTube"
|
||||
class="primary"
|
||||
/>
|
||||
<CtaLink
|
||||
@@ -54,7 +53,7 @@ const pods = podcastEpisodes(cache)
|
||||
platform="podcast"
|
||||
placement="hero"
|
||||
url={LINKS.podcast}
|
||||
label="Listen to the podcast"
|
||||
label="Listen to the Irregular Mind on Spotify"
|
||||
/>
|
||||
</div>
|
||||
<p class="muted" style="margin-top: 14px;">
|
||||
@@ -62,10 +61,9 @@ const pods = podcastEpisodes(cache)
|
||||
</p>
|
||||
</div>
|
||||
<div class="empty">
|
||||
<strong>Goal:</strong> 10% month-over-month growth in followers and engagement.
|
||||
<br />
|
||||
|
||||
<span class="muted"
|
||||
>This site is the SEO landing surface that turns search traffic into followers.</span
|
||||
>New videos and episodes every week. Start with the latest drops below.</span
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user