From aec8471b06ef77181259d84965f3561abb3daa4b Mon Sep 17 00:00:00 2001
From: Cory Dransfeldt <hi@coryd.dev>
Date: Sun, 17 Nov 2024 11:55:53 -0800
Subject: [PATCH] chore: myriad fixes + book year pages

---
 _headers                                     | 19 ++---
 public/feeds/feed.xsl                        | 74 ++++++++++++++++++++
 src/components/Footer.astro                  |  3 +-
 src/components/Header.astro                  |  3 +-
 src/components/Metadata.astro                |  5 +-
 src/components/blocks/BlockRenderer.astro    |  3 +-
 src/components/blocks/Hero.astro             | 23 +++++-
 src/components/home/RecentPosts.astro        |  2 +-
 src/components/media/Grid.astro              |  3 +-
 src/components/media/music/Recent.astro      |  4 +-
 src/components/media/watching/Hero.astro     |  4 +-
 src/layouts/Layout.astro                     |  8 +--
 src/pages/[permalink].astro                  |  4 +-
 src/pages/books/[isbn].astro                 |  5 +-
 src/pages/books/index.astro                  |  5 +-
 src/pages/books/years/[year].astro           | 46 ++++++++++++
 src/pages/feeds/{json => }/all.json.js       | 15 ++--
 src/pages/feeds/{rss => }/all.xml.js         | 17 ++---
 src/pages/feeds/books.json.js                | 23 ++++++
 src/pages/feeds/books.xml.js                 | 23 ++++++
 src/pages/feeds/json/books.json.js           | 22 ------
 src/pages/feeds/json/links.json.js           | 22 ------
 src/pages/feeds/json/posts.json.js           | 22 ------
 src/pages/feeds/links.json.js                | 23 ++++++
 src/pages/feeds/links.xml.js                 | 23 ++++++
 src/pages/feeds/{json => }/movies.json.js    | 15 ++--
 src/pages/feeds/movies.xml.js                | 23 ++++++
 src/pages/feeds/posts.json.js                | 23 ++++++
 src/pages/feeds/posts.xml.js                 | 23 ++++++
 src/pages/feeds/rss/books.xml.js             | 22 ------
 src/pages/feeds/rss/links.xml.js             | 22 ------
 src/pages/feeds/rss/movies.xml.js            | 22 ------
 src/pages/feeds/rss/posts.xml.js             | 22 ------
 src/pages/feeds/rss/syndication.xml.js       | 64 -----------------
 src/pages/feeds/syndication.xml.js           | 62 ++++++++++++++++
 src/pages/index.astro                        |  5 +-
 src/pages/links.astro                        |  6 +-
 src/pages/posts/[...page].astro              |  5 +-
 src/pages/posts/[year]/[title].astro         |  5 +-
 src/utils/data/artists.js                    |  2 +-
 src/utils/data/{ => dynamic}/bookByUrl.js    |  0
 src/utils/data/global/index.js               | 11 +++
 src/utils/generateRssFeed.js                 | 10 +--
 src/utils/{helpers.js => helpers/general.js} | 14 ++++
 src/utils/helpers/media.js                   | 44 ++++++++++++
 45 files changed, 508 insertions(+), 293 deletions(-)
 create mode 100644 public/feeds/feed.xsl
 create mode 100644 src/pages/books/years/[year].astro
 rename src/pages/feeds/{json => }/all.json.js (60%)
 rename src/pages/feeds/{rss => }/all.xml.js (54%)
 create mode 100644 src/pages/feeds/books.json.js
 create mode 100644 src/pages/feeds/books.xml.js
 delete mode 100644 src/pages/feeds/json/books.json.js
 delete mode 100644 src/pages/feeds/json/links.json.js
 delete mode 100644 src/pages/feeds/json/posts.json.js
 create mode 100644 src/pages/feeds/links.json.js
 create mode 100644 src/pages/feeds/links.xml.js
 rename src/pages/feeds/{json => }/movies.json.js (59%)
 create mode 100644 src/pages/feeds/movies.xml.js
 create mode 100644 src/pages/feeds/posts.json.js
 create mode 100644 src/pages/feeds/posts.xml.js
 delete mode 100644 src/pages/feeds/rss/books.xml.js
 delete mode 100644 src/pages/feeds/rss/links.xml.js
 delete mode 100644 src/pages/feeds/rss/movies.xml.js
 delete mode 100644 src/pages/feeds/rss/posts.xml.js
 delete mode 100644 src/pages/feeds/rss/syndication.xml.js
 create mode 100644 src/pages/feeds/syndication.xml.js
 rename src/utils/data/{ => dynamic}/bookByUrl.js (100%)
 create mode 100644 src/utils/data/global/index.js
 rename src/utils/{helpers.js => helpers/general.js} (84%)
 create mode 100644 src/utils/helpers/media.js

diff --git a/_headers b/_headers
index d870296..586089b 100644
--- a/_headers
+++ b/_headers
@@ -1,46 +1,39 @@
-/feeds/album-releases
-  Content-Type: application/xml; charset=utf-8
-  x-content-type-options: nosniff
-
-/feeds/album-releases.json
-  Content-Type: application/json
-
-/feeds/all
+/feeds/all.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
 /feeds/all.json
   Content-Type: application/json
 
-/feeds/books
+/feeds/books.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
 /feeds/books.json
   Content-Type: application/json
 
-/feeds/links
+/feeds/links.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
 /feeds/links.json
   Content-Type: application/json
 
-/feeds/posts
+/feeds/posts.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
 /feeds/posts.json
   Content-Type: application/json
 
-/feeds/movies
+/feeds/movies.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
 /feeds/movies.json
   Content-Type: application/json
 
-/feeds/syndication
+/feeds/syndication.xml
   Content-Type: application/xml; charset=utf-8
   x-content-type-options: nosniff
 
diff --git a/public/feeds/feed.xsl b/public/feeds/feed.xsl
new file mode 100644
index 0000000..f56741f
--- /dev/null
+++ b/public/feeds/feed.xsl
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+  xmlns:atom="http://www.w3.org/2005/Atom">
+  <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
+  <xsl:template match="/">
+    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+      <head>
+        <title>
+          <xsl:value-of select="/rss/channel/title" />
+        </title>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
+        <meta name="color-scheme" content="light dark" />
+        <link rel="stylesheet" href="/assets/styles/index.css" type="text/css" />
+      </head>
+      <body class="feed">
+        <div class="main-wrapper">
+          <main>
+            <section class="main-title">
+              <h1>
+                <a href="/feeds" tabindex="0">Cory Dransfeldt</a>
+              </h1>
+            </section>
+            <div class="default-wrapper">
+              <h2><xsl:value-of select="/rss/channel/title" /></h2>
+              <article class="intro">
+                <p>
+                  <xsl:value-of select="/rss/channel/description" />
+                </p>
+                <p>
+                  <strong class="highlight-text">Subscribe by adding the URL below to your feed reader
+                    of choice.</strong>
+                </p>
+                <p>
+                  <pre class="small">
+                    <code><xsl:value-of select="rss/channel/atom:link/@href"/></code>
+                  </pre>
+                </p>
+                <p>
+                  <a href="/feeds">View more of the feeds from my site.</a>
+                </p>
+              </article>
+              <section>
+                <xsl:for-each select="/rss/channel/item">
+                  <article>
+                    <time>Published: <xsl:value-of select="pubDate" /></time>
+                    <h3>
+                      <a>
+                        <xsl:attribute name="href">
+                          <xsl:value-of select="link" />
+                        </xsl:attribute>
+                        <xsl:value-of select="title" />
+                      </a>
+                    </h3>
+                    <xsl:value-of select="description" disable-output-escaping="yes" />
+                    <xsl:if test="enclosure">
+                      <img class="image-banner" src="{enclosure/@url}" alt="{title}" />
+                    </xsl:if>
+                  </article>
+                </xsl:for-each>
+              </section>
+            </div>
+          </main>
+          <footer>
+            <p>Subscribe by adding <code>
+                <xsl:value-of select="rss/channel/atom:link/@href" />
+              </code> to your
+              feed reader of choice.</p>
+          </footer>
+        </div>
+      </body>
+    </html>
+  </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
index 0f9bfd4..b467210 100644
--- a/src/components/Footer.astro
+++ b/src/components/Footer.astro
@@ -1,8 +1,9 @@
 ---
 import NavLink from '@components/nav/NavLink.astro';
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const { updated } = Astro.props;
-const { nav } = Astro.locals;
+const { nav } = await fetchGlobalData(Astro);
 ---
 
 <footer style={updated ? undefined : 'margin-top: var(--spacing-3xl)'}>
diff --git a/src/components/Header.astro b/src/components/Header.astro
index 44ba00e..3d7e809 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -1,8 +1,9 @@
 ---
 import Menu from '@components/nav/Menu.astro';
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const { siteName, url } = Astro.props;
-const { nav } = Astro.locals;
+const { nav } = await fetchGlobalData(Astro);
 const isHomePage = url === '/';
 ---
 
diff --git a/src/components/Metadata.astro b/src/components/Metadata.astro
index 4f70d52..f6830e3 100644
--- a/src/components/Metadata.astro
+++ b/src/components/Metadata.astro
@@ -1,5 +1,6 @@
 ---
-import { escapeHtml } from "@utils/helpers.js";
+import { escapeHtml } from "@utils/helpers/general.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const {
   schema = "page",
@@ -18,7 +19,7 @@ const {
   genre,
   year,
 } = Astro.props;
-const { globals} = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 
 let pageTitle = globals.site_name;
 let pageDescription = globals.site_description;
diff --git a/src/components/blocks/BlockRenderer.astro b/src/components/blocks/BlockRenderer.astro
index c182293..02c0c35 100644
--- a/src/components/blocks/BlockRenderer.astro
+++ b/src/components/blocks/BlockRenderer.astro
@@ -12,14 +12,13 @@ import Npm from '@components/blocks/banners/Npm.astro';
 import Rss from '@components/blocks/banners/Rss.astro';
 import YouTubePlayer from '@components/blocks//YouTubePlayer.astro';
 
-import { md } from '@utils/helpers.js';
+import { md } from '@utils/helpers/general.js';
 import { getPopularPosts } from '@utils/getPopularPosts.js';
 
 const analytics = await fetchAnalyticsData();
 const links = await fetchLinks();
 const posts = await fetchAllPosts();
 const popularPosts = getPopularPosts(posts, analytics);
-
 const { block } = Astro.props;
 ---
 
diff --git a/src/components/blocks/Hero.astro b/src/components/blocks/Hero.astro
index 7f32239..38d040a 100644
--- a/src/components/blocks/Hero.astro
+++ b/src/components/blocks/Hero.astro
@@ -1,7 +1,26 @@
 ---
+import { fetchGlobals } from "@utils/data/globals.js";
+
 const { image, alt } = Astro.props;
+const globals = await fetchGlobals(Astro);
 ---
 
 <div class="hero">
-  <img src={image} alt={alt} />
-</div>
\ No newline at end of file
+  <img
+    srcset={`
+      ${globals.cdn_url}${image}?class=bannersm&type=webp 256w,
+      ${globals.cdn_url}${image}?class=bannermd&type=webp 512w,
+      ${globals.cdn_url}${image}?class=bannerbase&type=webp 1024w
+    `}
+    sizes="(max-width: 450px) 256px,
+            (max-width: 850px) 512px,
+            1024px"
+    src={`${globals.cdn_url}${image}?class=bannersm&type=webp`}
+    alt={alt}
+    class="image-banner"
+    loading="lazy"
+    decoding="async"
+    width="720"
+    height="480"
+  />
+</div>
diff --git a/src/components/home/RecentPosts.astro b/src/components/home/RecentPosts.astro
index a222d3d..32be80c 100644
--- a/src/components/home/RecentPosts.astro
+++ b/src/components/home/RecentPosts.astro
@@ -1,7 +1,7 @@
 ---
 import { IconClock, IconStar, IconArrowRight } from '@tabler/icons-react';
 import { fetchAllPosts } from '@utils/data/posts.js';
-import { md } from '@utils/helpers.js';
+import { md } from '@utils/helpers/general.js';
 
 const posts = await fetchAllPosts();
 ---
diff --git a/src/components/media/Grid.astro b/src/components/media/Grid.astro
index 1842773..1d4bc3c 100644
--- a/src/components/media/Grid.astro
+++ b/src/components/media/Grid.astro
@@ -1,8 +1,9 @@
 ---
 import Paginator from '@components/nav/Paginator.astro';
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const { data, count, shape, pagination, loading = "lazy" } = Astro.props;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const pageCount = pagination?.pages?.length || 0;
 const hidePagination = pageCount <= 1;
 
diff --git a/src/components/media/music/Recent.astro b/src/components/media/music/Recent.astro
index 5cc0e3a..576d5e1 100644
--- a/src/components/media/music/Recent.astro
+++ b/src/components/media/music/Recent.astro
@@ -1,6 +1,8 @@
 ---
+import { fetchGlobalData } from '@utils/data/global/index.js';
+
 const { data } = Astro.props;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 ---
 
 <div class="music-chart">
diff --git a/src/components/media/watching/Hero.astro b/src/components/media/watching/Hero.astro
index fa72d4c..ce972dd 100644
--- a/src/components/media/watching/Hero.astro
+++ b/src/components/media/watching/Hero.astro
@@ -1,8 +1,8 @@
 ---
+import { fetchGlobalData } from '@utils/data/global/index.js';
 import Hero from "@components/blocks/Hero.astro";
 
 const { movie } = Astro.props;
-const { globals } = Astro.locals;
 ---
 
 <a href={movie.url}>
@@ -14,6 +14,6 @@ const { globals } = Astro.locals;
         ({movie.year})
       </div>
     </div>
-    <Hero globals={globals} image={movie.backdrop} alt={movie.title} />
+    <Hero image={movie.backdrop} alt={movie.title} />
   </div>
 </a>
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 734f023..d8966fd 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -3,10 +3,7 @@ import "@styles/index.css";
 import Header from "@components/Header.astro";
 import Footer from "@components/Footer.astro";
 import Metadata from "@components/Metadata.astro";
-import { fetchNavigation } from "@utils/data/nav.js";
-
-const currentUrl = Astro.url.pathname;
-const nav = await fetchNavigation();
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const {
   schema = "page",
@@ -17,7 +14,8 @@ const {
   updated,
   ...otherProps
 } = Astro.props;
-const { globals} = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
+const currentUrl = Astro.url.pathname;
 const isProduction = import.meta.env.MODE === "production";
 ---
 
diff --git a/src/pages/[permalink].astro b/src/pages/[permalink].astro
index 6379606..2479542 100644
--- a/src/pages/[permalink].astro
+++ b/src/pages/[permalink].astro
@@ -2,6 +2,7 @@
 import Layout from '@layouts/Layout.astro';
 import BlockRenderer from '@components/blocks/BlockRenderer.astro';
 import { fetchPages } from '@utils/data/pages';
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 export const prerender = true;
 
@@ -14,12 +15,11 @@ export async function getStaticPaths() {
 }
 
 const { page } = Astro.props;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const currentUrl = Astro.url.pathname;
 ---
 
 <Layout
-  globals={globals}
   pageTitle={page.title}
   description={page.description}
   ogImage={page.open_graph_image}
diff --git a/src/pages/books/[isbn].astro b/src/pages/books/[isbn].astro
index 32ab6ac..7d6e566 100644
--- a/src/pages/books/[isbn].astro
+++ b/src/pages/books/[isbn].astro
@@ -4,7 +4,8 @@ import Warning from "@components/blocks/banners/Warning.astro";
 import AssociatedMedia from "@components/blocks/AssociatedMedia.astro";
 import ProgressBar from "@components/media/ProgressBar.astro";
 import { IconArrowLeft, IconHeart, IconNeedle } from "@tabler/icons-react";
-import { fetchBookByUrl } from "@utils/data/bookByUrl.js";
+import { fetchBookByUrl } from "@utils/data/dynamic/bookByUrl.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
 const { isbn } = Astro.params;
 
@@ -14,7 +15,7 @@ if (!book) return Astro.redirect("/404", 404);
 const alt = `${book.title}${book.author ? ` by ${book.author}` : ""}`;
 const pageTitle = `Books / ${book.title}`;
 const description = book.description || `Details about the book ${book.title}`;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 ---
 
 <Layout pageTitle={pageTitle} description={description} schema="book">
diff --git a/src/pages/books/index.astro b/src/pages/books/index.astro
index 3d85bdd..2d78809 100644
--- a/src/pages/books/index.astro
+++ b/src/pages/books/index.astro
@@ -3,10 +3,11 @@ import Layout from "@layouts/Layout.astro";
 import Rss from "@components/blocks/banners/Rss.astro";
 import ProgressBar from "@components/media/ProgressBar.astro";
 import { fetchBooks } from "@utils/data/books.js";
-import { md, htmlTruncate } from "@utils/helpers.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
+import { md, htmlTruncate } from "@utils/helpers/general.js";
 
 const books = await fetchBooks();
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const title = "Currently reading";
 const description = "Here's what I'm reading at the moment.";
 const updated = new Date().toISOString();
diff --git a/src/pages/books/years/[year].astro b/src/pages/books/years/[year].astro
new file mode 100644
index 0000000..54b2321
--- /dev/null
+++ b/src/pages/books/years/[year].astro
@@ -0,0 +1,46 @@
+---
+import Layout from "@layouts/Layout.astro";
+import Grid from "@components/media/Grid.astro";
+import { IconArrowLeft } from "@tabler/icons-react";
+import { filterBooksByStatus, findFavoriteBooks, mediaLinks } from "@utils/helpers/media.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
+import { fetchBooks } from "@utils/data/books.js";
+import { DateTime } from "luxon";
+
+const { globals } = await fetchGlobalData(Astro);
+const books = await fetchBooks();
+const { year } = Astro.params;
+const yearData = books.years.find((y) => y.value === parseInt(year, 10));
+
+if (!yearData) return Astro.redirect("/404", 404);
+
+const bookData = filterBooksByStatus(yearData.data, "finished");
+const bookDataFavorites = findFavoriteBooks(bookData);
+const favoriteBooks = mediaLinks(bookDataFavorites, "book", 5);
+const currentYear = DateTime.now().year;
+const isCurrentYear = parseInt(year, 10) === currentYear;
+const pageTitle = `${year} / Books`;
+const description = isCurrentYear
+  ? `I've finished ${bookData.length} books this year.`
+  : `I finished ${bookData.length} books in ${year}.`;
+const intro = isCurrentYear
+  ? `
+    I've finished <strong class="highlight-text">${bookData.length} books</strong> this year.
+    ${favoriteBooks ? ` Among my favorites are ${favoriteBooks}.` : ''}
+  `
+  : `
+    I finished <strong class="highlight-text">${bookData.length} books</strong> in 
+    <strong class="highlight-text">${year}</strong>.
+    ${favoriteBooks ? ` Among my favorites were ${favoriteBooks}.` : ''}
+  `;
+---
+
+<Layout globals={globals} pageTitle={pageTitle} description={description} schema="books-year">
+  <a href="/books" class="back-link">
+    <IconArrowLeft size={18} /> Back to books
+  </a>
+  <h2 class="page-title">{year} / Books</h2>
+  <div set:html={intro}></div>
+  <hr />
+  <Grid globals={globals} data={bookData} shape="vertical" count={200} loading="eager" />
+</Layout>
diff --git a/src/pages/feeds/json/all.json.js b/src/pages/feeds/all.json.js
similarity index 60%
rename from src/pages/feeds/json/all.json.js
rename to src/pages/feeds/all.json.js
index 6cc1a4d..3c02bc7 100644
--- a/src/pages/feeds/json/all.json.js
+++ b/src/pages/feeds/all.json.js
@@ -1,8 +1,10 @@
 import { generateJsonFeed } from '@utils/generateJsonFeed';
 import { fetchGlobals } from '@utils/data/globals';
 import { fetchActivity } from '@utils/data/activity';
+import fs from 'fs/promises';
+import path from 'path';
 
-export async function GET() {
+export async function getStaticPaths() {
   const globals = await fetchGlobals();
   const activity = await fetchActivity();
 
@@ -13,10 +15,9 @@ export async function GET() {
     data: activity,
   });
 
-  return new Response(feed, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/json",
-    },
-  });
+  const filePath = path.resolve('public/feeds/all.json');
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, feed);
+
+  return [];
 }
diff --git a/src/pages/feeds/rss/all.xml.js b/src/pages/feeds/all.xml.js
similarity index 54%
rename from src/pages/feeds/rss/all.xml.js
rename to src/pages/feeds/all.xml.js
index 77bac08..bc37160 100644
--- a/src/pages/feeds/rss/all.xml.js
+++ b/src/pages/feeds/all.xml.js
@@ -1,22 +1,23 @@
 import { generateRssFeed } from "@utils/generateRssFeed";
 import { fetchGlobals } from "@utils/data/globals";
 import { fetchActivity } from "@utils/data/activity";
+import fs from "fs/promises";
+import path from "path";
 
-export async function GET() {
+export async function getStaticPaths() {
   const globals = await fetchGlobals();
   const activity = await fetchActivity();
 
   const rss = generateRssFeed({
     permalink: "/feeds/all.xml",
-    title: "All activity / Cory Dransfeldt",
+    title: "All activity feed",
     globals,
     data: activity,
   });
 
-  return new Response(rss, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/rss+xml",
-    },
-  });
+  const filePath = path.resolve("public/feeds/all.xml");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, rss);
+
+  return [];
 }
diff --git a/src/pages/feeds/books.json.js b/src/pages/feeds/books.json.js
new file mode 100644
index 0000000..6d6838d
--- /dev/null
+++ b/src/pages/feeds/books.json.js
@@ -0,0 +1,23 @@
+import { generateJsonFeed } from "@utils/generateJsonFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchBooks } from "@utils/data/books";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const books = await fetchBooks();
+
+  const feed = generateJsonFeed({
+    permalink: "/feeds/books.json",
+    title: "Books / Cory Dransfeldt",
+    globals,
+    data: books.feed,
+  });
+
+  const filePath = path.resolve("public/feeds/books.json");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, feed);
+
+  return [];
+}
diff --git a/src/pages/feeds/books.xml.js b/src/pages/feeds/books.xml.js
new file mode 100644
index 0000000..955feed
--- /dev/null
+++ b/src/pages/feeds/books.xml.js
@@ -0,0 +1,23 @@
+import { generateRssFeed } from "@utils/generateRssFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchBooks } from "@utils/data/books";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const books = await fetchBooks();
+
+  const rss = generateRssFeed({
+    permalink: "/feeds/books.xml",
+    title: "Books feed",
+    globals,
+    data: books.feed,
+  });
+
+  const filePath = path.resolve("public/feeds/books.xml");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, rss);
+
+  return [];
+}
diff --git a/src/pages/feeds/json/books.json.js b/src/pages/feeds/json/books.json.js
deleted file mode 100644
index 54e2a61..0000000
--- a/src/pages/feeds/json/books.json.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateJsonFeed } from '@utils/generateJsonFeed';
-import { fetchGlobals } from '@utils/data/globals';
-import { fetchBooks } from '@utils/data/books';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const books = await fetchBooks();
-
-  const feed = generateJsonFeed({
-    permalink: "/feeds/books.json",
-    title: "Books / Cory Dransfeldt",
-    globals,
-    data: books.feed,
-  });
-
-  return new Response(feed, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/json",
-    },
-  });
-}
diff --git a/src/pages/feeds/json/links.json.js b/src/pages/feeds/json/links.json.js
deleted file mode 100644
index 98fdd5f..0000000
--- a/src/pages/feeds/json/links.json.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateJsonFeed } from '@utils/generateJsonFeed';
-import { fetchGlobals } from '@utils/data/globals';
-import { fetchLinks } from '@utils/data/links';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const links = await fetchLinks();
-
-  const feed = generateJsonFeed({
-    permalink: "/feeds/links.json",
-    title: "Links / Cory Dransfeldt",
-    globals,
-    data: links,
-  });
-
-  return new Response(feed, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/json",
-    },
-  });
-}
diff --git a/src/pages/feeds/json/posts.json.js b/src/pages/feeds/json/posts.json.js
deleted file mode 100644
index ea6e978..0000000
--- a/src/pages/feeds/json/posts.json.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateJsonFeed } from '@utils/generateJsonFeed';
-import { fetchGlobals } from '@utils/data/globals';
-import { fetchAllPosts } from '@utils/data/posts';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const posts = await fetchAllPosts();
-
-  const feed = generateJsonFeed({
-    permalink: "/feeds/posts.json",
-    title: "Posts / Cory Dransfeldt",
-    globals,
-    data: posts,
-  });
-
-  return new Response(feed, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/json",
-    },
-  });
-}
diff --git a/src/pages/feeds/links.json.js b/src/pages/feeds/links.json.js
new file mode 100644
index 0000000..a84243e
--- /dev/null
+++ b/src/pages/feeds/links.json.js
@@ -0,0 +1,23 @@
+import { generateJsonFeed } from "@utils/generateJsonFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchLinks } from "@utils/data/links";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const links = await fetchLinks();
+
+  const feed = generateJsonFeed({
+    permalink: "/feeds/links.json",
+    title: "Links / Cory Dransfeldt",
+    globals,
+    data: links,
+  });
+
+  const filePath = path.resolve("public/feeds/links.json");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, feed);
+
+  return [];
+}
diff --git a/src/pages/feeds/links.xml.js b/src/pages/feeds/links.xml.js
new file mode 100644
index 0000000..57913a5
--- /dev/null
+++ b/src/pages/feeds/links.xml.js
@@ -0,0 +1,23 @@
+import { generateRssFeed } from "@utils/generateRssFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchLinks } from "@utils/data/links";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const links = await fetchLinks();
+
+  const rss = generateRssFeed({
+    permalink: "/feeds/links.xml",
+    title: "Links feed",
+    globals,
+    data: links,
+  });
+
+  const filePath = path.resolve("public/feeds/links.xml");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, rss);
+
+  return [];
+}
diff --git a/src/pages/feeds/json/movies.json.js b/src/pages/feeds/movies.json.js
similarity index 59%
rename from src/pages/feeds/json/movies.json.js
rename to src/pages/feeds/movies.json.js
index 852eeea..bebd3ee 100644
--- a/src/pages/feeds/json/movies.json.js
+++ b/src/pages/feeds/movies.json.js
@@ -1,8 +1,10 @@
 import { generateJsonFeed } from '@utils/generateJsonFeed';
 import { fetchGlobals } from '@utils/data/globals';
 import { fetchMovies } from '@utils/data/movies';
+import fs from 'fs/promises';
+import path from 'path';
 
-export async function GET() {
+export async function getStaticPaths() {
   const globals = await fetchGlobals();
   const movies = await fetchMovies();
 
@@ -13,10 +15,9 @@ export async function GET() {
     data: movies.feed,
   });
 
-  return new Response(feed, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/json",
-    },
-  });
+  const filePath = path.resolve("public/feeds/movies.json");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, feed);
+
+  return [];
 }
diff --git a/src/pages/feeds/movies.xml.js b/src/pages/feeds/movies.xml.js
new file mode 100644
index 0000000..688365a
--- /dev/null
+++ b/src/pages/feeds/movies.xml.js
@@ -0,0 +1,23 @@
+import { generateRssFeed } from "@utils/generateRssFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchMovies } from "@utils/data/movies";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const movies = await fetchMovies();
+
+  const rss = generateRssFeed({
+    permalink: "/feeds/movies.xml",
+    title: "Movies feed",
+    globals,
+    data: movies.feed,
+  });
+
+  const filePath = path.resolve("public/feeds/movies.xml");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, rss);
+
+  return [];
+}
diff --git a/src/pages/feeds/posts.json.js b/src/pages/feeds/posts.json.js
new file mode 100644
index 0000000..50d9424
--- /dev/null
+++ b/src/pages/feeds/posts.json.js
@@ -0,0 +1,23 @@
+import { generateJsonFeed } from "@utils/generateJsonFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchAllPosts } from "@utils/data/posts";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const posts = await fetchAllPosts();
+
+  const feed = generateJsonFeed({
+    permalink: "/feeds/posts.json",
+    title: "Posts / Cory Dransfeldt",
+    globals,
+    data: posts,
+  });
+
+  const filePath = path.resolve("public/feeds/posts.json");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, feed);
+
+  return [];
+}
diff --git a/src/pages/feeds/posts.xml.js b/src/pages/feeds/posts.xml.js
new file mode 100644
index 0000000..84ac3c1
--- /dev/null
+++ b/src/pages/feeds/posts.xml.js
@@ -0,0 +1,23 @@
+import { generateRssFeed } from "@utils/generateRssFeed";
+import { fetchGlobals } from "@utils/data/globals";
+import { fetchAllPosts } from "@utils/data/posts";
+import fs from "fs/promises";
+import path from "path";
+
+export async function getStaticPaths() {
+  const globals = await fetchGlobals();
+  const posts = await fetchAllPosts();
+
+  const rss = generateRssFeed({
+    permalink: "/feeds/posts.xml",
+    title: "Posts feed",
+    globals,
+    data: posts,
+  });
+
+  const filePath = path.resolve("public/feeds/posts.xml");
+  await fs.mkdir(path.dirname(filePath), { recursive: true });
+  await fs.writeFile(filePath, rss);
+
+  return [];
+}
diff --git a/src/pages/feeds/rss/books.xml.js b/src/pages/feeds/rss/books.xml.js
deleted file mode 100644
index 187c346..0000000
--- a/src/pages/feeds/rss/books.xml.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateRssFeed } from "@utils/generateRssFeed";
-import { fetchGlobals } from "@utils/data/globals";
-import { fetchBooks } from '@utils/data/books';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const books = await fetchBooks();
-
-  const rss = generateRssFeed({
-    permalink: "/feeds/books.xml",
-    title: "Books / Cory Dransfeldt",
-    globals,
-    data: books.feed,
-  });
-
-  return new Response(rss, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/rss+xml",
-    },
-  });
-}
diff --git a/src/pages/feeds/rss/links.xml.js b/src/pages/feeds/rss/links.xml.js
deleted file mode 100644
index e2219cd..0000000
--- a/src/pages/feeds/rss/links.xml.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateRssFeed } from "@utils/generateRssFeed";
-import { fetchGlobals } from "@utils/data/globals";
-import { fetchLinks } from '@utils/data/links';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const links = await fetchLinks();
-
-  const rss = generateRssFeed({
-    permalink: "/feeds/links.xml",
-    title: "Links / Cory Dransfeldt",
-    globals,
-    data: links,
-  });
-
-  return new Response(rss, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/rss+xml",
-    },
-  });
-}
diff --git a/src/pages/feeds/rss/movies.xml.js b/src/pages/feeds/rss/movies.xml.js
deleted file mode 100644
index 78a314d..0000000
--- a/src/pages/feeds/rss/movies.xml.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateRssFeed } from "@utils/generateRssFeed";
-import { fetchGlobals } from "@utils/data/globals";
-import { fetchMovies } from '@utils/data/movies';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const movies = await fetchMovies();
-
-  const rss = generateRssFeed({
-    permalink: "/feeds/movies.xml",
-    title: "Movies / Cory Dransfeldt",
-    globals,
-    data: movies.feed,
-  });
-
-  return new Response(rss, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/rss+xml",
-    },
-  });
-}
diff --git a/src/pages/feeds/rss/posts.xml.js b/src/pages/feeds/rss/posts.xml.js
deleted file mode 100644
index 53f9890..0000000
--- a/src/pages/feeds/rss/posts.xml.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { generateRssFeed } from "@utils/generateRssFeed";
-import { fetchGlobals } from "@utils/data/globals";
-import { fetchAllPosts } from '@utils/data/posts';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const posts = await fetchAllPosts();
-
-  const rss = generateRssFeed({
-    permalink: "/feeds/posts.xml",
-    title: "Posts / Cory Dransfeldt",
-    globals,
-    data: posts,
-  });
-
-  return new Response(rss, {
-    status: 200,
-    headers: {
-      "Content-Type": "application/rss+xml",
-    },
-  });
-}
diff --git a/src/pages/feeds/rss/syndication.xml.js b/src/pages/feeds/rss/syndication.xml.js
deleted file mode 100644
index b75af08..0000000
--- a/src/pages/feeds/rss/syndication.xml.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import fetchSyndication from '@utils/data/syndication.js';
-import { fetchGlobals } from '@utils/data/globals.js';
-
-export async function GET() {
-  const globals = await fetchGlobals();
-  const entries = await fetchSyndication();
-
-  if (!entries.length) return new Response('No feed entries found.', { status: 404 });
-
-  const title = globals.site_name || 'Syndicated content / Cory Dransfeldt';
-  const permalink = '/feeds/syndication.xml';
-
-  const xml = `<?xml version="1.0" encoding="UTF-8" ?>
-<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
-  <channel>
-    <atom:link href="${globals.url}${permalink}" rel="self" type="application/rss+xml" />
-    <title><![CDATA[${title}]]></title>
-    <description><![CDATA[${globals.site_description || ''}]]></description>
-    <link>${globals.url}${permalink}</link>
-    <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
-    <image>
-      <title><![CDATA[${title}]]></title>
-      <link>${globals.url}${permalink}</link>
-      <url>${globals.cdn_url}${globals.avatar}?class=w200</url>
-      <width>144</width>
-      <height>144</height>
-    </image>
-    ${entries
-      .slice(0, 20)
-      .map(
-        (entry) => `
-    <item>
-      <title><![CDATA[${entry.syndication.title}]]></title>
-      <link>${encodeAmp(entry.syndication.url)}</link>
-      <pubDate>${new Date(entry.syndication.date).toUTCString()}</pubDate>
-      <guid isPermaLink="false">${encodeAmp(entry.syndication.url)}</guid>
-      <description><![CDATA[${escapeHTML(entry.syndication.description)}]]></description>
-    </item>`
-      )
-      .join('')}
-  </channel>
-</rss>`;
-
-  return new Response(xml, {
-    status: 200,
-    headers: {
-      'Content-Type': 'application/rss+xml',
-    },
-  });
-}
-
-function encodeAmp(url) {
-  return url.replace(/&/g, '&amp;');
-}
-
-function escapeHTML(str) {
-  if (!str) return '';
-  return str
-    .replace(/&/g, '&amp;')
-    .replace(/</g, '&lt;')
-    .replace(/>/g, '&gt;')
-    .replace(/"/g, '&quot;')
-    .replace(/'/g, '&#039;');
-}
\ No newline at end of file
diff --git a/src/pages/feeds/syndication.xml.js b/src/pages/feeds/syndication.xml.js
new file mode 100644
index 0000000..4135729
--- /dev/null
+++ b/src/pages/feeds/syndication.xml.js
@@ -0,0 +1,62 @@
+import { DateTime } from "luxon";
+import fetchSyndication from '@utils/data/syndication.js';
+import { fetchGlobals } from '@utils/data/globals.js';
+import { dateToRFC822, encodeAmp, md } from '@utils/helpers/general.js';
+
+const generateSyndicationRSS = async () => {
+  const globals = await fetchGlobals();
+  const entries = await fetchSyndication();
+
+  if (!entries.length) throw new Error('No feed entries found.');
+
+  const title = globals.site_name || 'Syndicated Content Feed';
+  const permalink = '/feeds/syndication.xml';
+
+  const xml = `<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+  <channel>
+    <atom:link href="${globals.url}${permalink}" rel="self" type="application/rss+xml" />
+    <title><![CDATA[${title}]]></title>
+    <description><![CDATA[${globals.site_description || ''}]]></description>
+    <link>${globals.url}</link>
+    <lastBuildDate>${dateToRFC822(DateTime.now())}</lastBuildDate>
+    <image>
+      <title><![CDATA[${title}]]></title>
+      <link>${globals.url}</link>
+      <url>${globals.cdn_url}${globals.avatar}?class=w200</url>
+      <width>144</width>
+      <height>144</height>
+    </image>
+    ${entries
+      .slice(0, 20)
+      .map(
+        (entry) => `
+    <item>
+      <title><![CDATA[${entry.syndication.title || 'Untitled'}]]></title>
+      <link>${encodeAmp(entry.syndication.url)}</link>
+      <pubDate>${dateToRFC822(entry.syndication.date)}</pubDate>
+      <guid isPermaLink="false">${encodeAmp(entry.syndication.url)}</guid>
+      <description><![CDATA[${md(entry.syndication.description || '')}]]></description>
+    </item>`
+      )
+      .join('')}
+  </channel>
+</rss>`;
+
+  return xml;
+};
+
+export async function GET() {
+  try {
+    const rss = await generateSyndicationRSS();
+    return new Response(rss, {
+      status: 200,
+      headers: { 'Content-Type': 'application/rss+xml' },
+    });
+  } catch (error) {
+    console.error(error.message);
+    return new Response('Error generating syndication feed.', { status: 500 });
+  }
+}
+
+export const prerender = true;
diff --git a/src/pages/index.astro b/src/pages/index.astro
index fae8b66..d21e416 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -3,8 +3,9 @@ import Layout from '@layouts/Layout.astro';
 import Intro from '@components/home/Intro.astro';
 import RecentActivity from '@components/home/RecentActivity.astro';
 import RecentPosts from '@components/home/RecentPosts.astro';
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const schema = 'blog';
 const pageTitle = globals.site_name;
 const description = 'This is a blog post description';
@@ -13,14 +14,12 @@ const fullUrl = globals.url + '/blog/my-post';
 const themeColor = globals.theme_color;
 ---
 <Layout
-  globals={globals}
   pageTitle={pageTitle}
   description={description}
   ogImage={ogImage}
   fullUrl={fullUrl}
   themeColor={themeColor}
   schema={schema}
-  globals={globals}
 >
   <Intro intro={globals.intro} />
   <RecentActivity />
diff --git a/src/pages/links.astro b/src/pages/links.astro
index 1965349..38fcb52 100644
--- a/src/pages/links.astro
+++ b/src/pages/links.astro
@@ -3,14 +3,12 @@ import Layout from "@layouts/Layout.astro";
 import Paginator from "@components/nav/Paginator.astro";
 import RssBanner from "@components/blocks/banners/Rss.astro";
 import { fetchLinks } from "@utils/data/links.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
 
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const links = await fetchLinks();
-
 const title = "Links";
 const description = "These are links I've liked or otherwise found interesting. They're all added manually, after having been read and, I suppose, properly considered.";
-
-// Pagination Settings
 const pageSize = 30;
 const currentPage = parseInt(Astro.url.searchParams.get("page") || "1", 10);
 const totalPages = Math.ceil(links.length / pageSize);
diff --git a/src/pages/posts/[...page].astro b/src/pages/posts/[...page].astro
index a2dc7ba..28343fa 100644
--- a/src/pages/posts/[...page].astro
+++ b/src/pages/posts/[...page].astro
@@ -2,14 +2,15 @@
 import { getCollection } from 'astro:content';
 import { IconStar } from '@tabler/icons-react';
 import { fetchAllPosts } from "@data/posts.js";
+import { fetchGlobalData } from '@utils/data/global/index.js';
 import Layout from "@layouts/Layout.astro";
 import Paginator from '@components/nav/Paginator.astro';
-import { md } from '@utils/helpers.js';
+import { md } from '@utils/helpers/general.js';
 import { DateTime } from 'luxon';
 
 const posts = await fetchAllPosts();
 const { page } = Astro.props;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const currentUrl = Astro.url.pathname;
 
 const currentPage = Astro.params.page ? parseInt(Astro.params.page, 10) : 1;
diff --git a/src/pages/posts/[year]/[title].astro b/src/pages/posts/[year]/[title].astro
index 566ee50..6fc69cf 100644
--- a/src/pages/posts/[year]/[title].astro
+++ b/src/pages/posts/[year]/[title].astro
@@ -3,7 +3,8 @@ import { IconStar } from "@tabler/icons-react";
 import { fetchAllPosts } from "@data/posts.js";
 import { fetchAnalyticsData } from "@data/analytics.js";
 import { fetchLinks } from "@data/links.js";
-import { md } from '@utils/helpers.js';
+import { fetchGlobalData } from '@utils/data/global/index.js';
+import { md } from '@utils/helpers/general.js';
 import { getPopularPosts } from '@utils/getPopularPosts.js';
 
 const analytics = await fetchAnalyticsData();
@@ -38,7 +39,7 @@ export async function getStaticPaths() {
 }
 
 const { post } = Astro.props;
-const { globals } = Astro.locals;
+const { globals } = await fetchGlobalData(Astro);
 const { year, title } = Astro.params;
 const currentUrl = Astro.url.pathname;
 const htmlContent = md(post.content);
diff --git a/src/utils/data/artists.js b/src/utils/data/artists.js
index b5482dc..fdcc344 100644
--- a/src/utils/data/artists.js
+++ b/src/utils/data/artists.js
@@ -1,5 +1,5 @@
 import { createClient } from "@supabase/supabase-js";
-import { parseCountryField } from "@utils/helpers.js";
+import { parseCountryField } from "@utils/helpers/general.js";
 
 const SUPABASE_URL = import.meta.env.SUPABASE_URL;
 const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
diff --git a/src/utils/data/bookByUrl.js b/src/utils/data/dynamic/bookByUrl.js
similarity index 100%
rename from src/utils/data/bookByUrl.js
rename to src/utils/data/dynamic/bookByUrl.js
diff --git a/src/utils/data/global/index.js b/src/utils/data/global/index.js
new file mode 100644
index 0000000..0f2277b
--- /dev/null
+++ b/src/utils/data/global/index.js
@@ -0,0 +1,11 @@
+import { fetchGlobals } from "@utils/data/globals.js";
+import { fetchNavigation } from "@utils/data/nav.js";
+
+export async function fetchGlobalData(Astro) {
+  if (Astro?.locals) return Astro.locals;
+
+  const globals = await fetchGlobals();
+  const nav = await fetchNavigation();
+
+  return { globals, nav };
+}
diff --git a/src/utils/generateRssFeed.js b/src/utils/generateRssFeed.js
index f0c347f..e156f8c 100644
--- a/src/utils/generateRssFeed.js
+++ b/src/utils/generateRssFeed.js
@@ -1,4 +1,5 @@
 import { DateTime } from "luxon";
+import { dateToRFC822, encodeAmp, md } from '@utils/helpers/general.js';
 
 export function generateRssFeed({ permalink, title, globals, data }) {
   const rssItems = data.slice(0, 20).map((entry) => {
@@ -11,27 +12,28 @@ export function generateRssFeed({ permalink, title, globals, data }) {
     return `
       <item>
         <title><![CDATA[${entryTitle}]]></title>
-        <link>${entryFeed.url}</link>
-        <pubDate>${DateTime.fromISO(entryFeed.date).toRFC2822()}</pubDate>
+        <link>${encodeAmp(entryFeed.url)}</link>
+        <pubDate>${dateToRFC822(entryFeed.date)}</pubDate>
         <guid isPermaLink="false">${entryFeed.url}</guid>
         ${
           entryFeed.image
             ? `<enclosure url="${globals.cdn_url}${entryFeed.image}?class=w800&type=jpg" type="image/jpeg" />`
             : ""
         }
-        <description><![CDATA[${entryFeed.description}]]></description>
+        <description><![CDATA[${md(entryFeed.description)}]]></description>
       </item>`;
   });
 
   return `
 <?xml version="1.0" encoding="UTF-8" ?>
+<?xml-stylesheet href="/feeds/feed.xsl" type="text/xsl" ?>
 <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
   <channel>
     <atom:link href="${globals.url}${permalink}" rel="self" type="application/rss+xml" />
     <title><![CDATA[${title}]]></title>
     <description><![CDATA[${globals.site_description}]]></description>
     <link>${globals.url}${permalink}</link>
-    <lastBuildDate>${DateTime.now().toUTC().toRFC2822()}</lastBuildDate>
+    <lastBuildDate>${dateToRFC822(DateTime.now())}</lastBuildDate>
     <image>
       <title><![CDATA[${title}]]></title>
       <link>${globals.url}${permalink}</link>
diff --git a/src/utils/helpers.js b/src/utils/helpers/general.js
similarity index 84%
rename from src/utils/helpers.js
rename to src/utils/helpers/general.js
index 97252f3..14d3795 100644
--- a/src/utils/helpers.js
+++ b/src/utils/helpers/general.js
@@ -1,3 +1,4 @@
+import { DateTime } from "luxon";
 import markdownIt from "markdown-it";
 import markdownItAnchor from "markdown-it-anchor";
 import markdownItFootnote from "markdown-it-footnote";
@@ -14,6 +15,7 @@ markdown.use(markdownItAnchor, {
 markdown.use(markdownItFootnote);
 markdown.use(markdownItPrism);
 
+// arrays
 export const shuffleArray = (array) => {
   const shuffled = [...array];
   for (let i = shuffled.length - 1; i > 0; i--) {
@@ -25,6 +27,7 @@ export const shuffleArray = (array) => {
   return shuffled;
 };
 
+// countries
 export const regionNames = new Intl.DisplayNames(["en"], { type: "region" });
 
 export const getCountryName = (countryCode) =>
@@ -43,8 +46,10 @@ export const parseCountryField = (countryField) => {
   return countries.map(getCountryName).join(", ");
 };
 
+// markdown
 export const md = (string) => markdown.render(string);
 
+// html
 export const htmlTruncate = (content, limit = 50) =>
   truncateHtml(content, limit, {
     byWords: true,
@@ -63,3 +68,12 @@ export const escapeHtml = (str) =>
         ">": "&gt;",
       }[char] || char)
   );
+
+// urls
+export const encodeAmp = (url) => url.replace(/&/g, "&amp;");
+
+// dates
+export const dateToRFC822 = (date) =>
+  DateTime.fromJSDate(date, { zone: "America/Los_Angeles" }).toFormat(
+    "ccc, dd LLL yyyy HH:mm:ss ZZZ"
+  );
diff --git a/src/utils/helpers/media.js b/src/utils/helpers/media.js
new file mode 100644
index 0000000..996a9b8
--- /dev/null
+++ b/src/utils/helpers/media.js
@@ -0,0 +1,44 @@
+export const filterBooksByStatus = (books, status) =>
+  books.filter((book) => book["status"] === status);
+
+export const findFavoriteBooks = (books) =>
+  books.filter((book) => book["favorite"] === true);
+
+export const bookYearLinks = (years) =>
+  years
+    .sort((a, b) => b["value"] - a["value"])
+    .map(
+      (year, index) =>
+        `<a href="/books/years/${year["value"]}">${year["value"]}</a>${
+          index < years.length - 1 ? " / " : ""
+        }`
+    )
+    .join("");
+
+export const mediaLinks = (data, type, count = 10) => {
+  if (!data || !type) return "";
+
+  const dataSlice = data.slice(0, count);
+  if (dataSlice.length === 0) return null;
+
+  const buildLink = (item) => {
+    switch (type) {
+      case "genre":
+        return `<a href="${item["genre_url"]}">${item["genre_name"]}</a>`;
+      case "artist":
+        return `<a href="${item["url"]}">${item["name"]}</a>`;
+      case "book":
+        return `<a href="${item["url"]}">${item["title"]}</a>`;
+      default:
+        return "";
+    }
+  };
+
+  if (dataSlice.length === 1) return buildLink(dataSlice[0]);
+
+  const links = dataSlice.map(buildLink);
+  const allButLast = links.slice(0, -1).join(", ");
+  const last = links[links.length - 1];
+
+  return `${allButLast} and ${last}`;
+};