<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[Blog | Jeremy Harland]]></title>
        <description><![CDATA[Random thoughts, project updates and tech musings from Jeremy.]]></description>
        <link>https://www.jeremyharland.com</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Mon, 27 Apr 2026 22:38:40 GMT</lastBuildDate>
        <atom:link href="https://www.jeremyharland.com/rss.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Mon, 27 Apr 2026 22:38:40 GMT</pubDate>
        <copyright><![CDATA[All rights unreserved 2026]]></copyright>
        <language><![CDATA[en]]></language>
        <item>
            <title><![CDATA[The art of over engineering]]></title>
            <description><![CDATA[Discussion and ramble on making silly design decisions]]></description>
            <link>https://www.jeremyharland.com/blog/art-of-over-engineering</link>
            <guid isPermaLink="true">https://www.jeremyharland.com/blog/art-of-over-engineering</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Jeremy Harland]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="/images/blog/bob-ross.jpg" length="0" type="image/jpeg"/>
            <content:encoded><![CDATA[<p>Nearing 6 months ago I set out on one of my days off to create a website to facilitate 3 goals:</p>
<ol>
<li>showcasing my skills for potential employers</li>
<li>Sharing my music with my friends</li>
<li>starting a blog about nothing in particular</li>
</ol>
<p>The first two of this list were checked off by whipping up a quick portfolio site with a nifty frontend wrapper to display my listening history from <a href="https://www.last.fm/user/Jeznado">last.fm</a>. I used <a href="https://tanstack.com/start/latest">tanstack start</a> to do this as I fear the triangle man at nextjs and it is always a great opportunity to try a new technology to learn when doing a low risk project.</p>
<p>All three of these goals are small enough tasks which many people before me have taken on.</p>
<h3 id="so-what-went-wrong-with-the-blog"><a class="anchor" aria-hidden="true" tabindex="-1" href="#so-what-went-wrong-with-the-blog"><span class="icon icon-link"></span></a>So what went wrong with the blog?</h3>
<p><strong>Defining the problem</strong></p>
<p>When taking on this personal task I didn't handle it as if I were at work. Consulting stakeholders, data analysis to define what actually needs to be tackled to generate an adequate solution.</p>
<p><strong>Where I ended up</strong></p>
<p>Being driven by wanting to use the hottest technology options led me the poorest solution. I created a postgres database with neon and stored my images in vercel blob, created a markdown text editor that I had to login to use on my site with github SSO.</p>
<p>Lets circle back to the problem. I want the ability to create blog posts. Fullstop I didn't need a shitty editor I whipped up that crashed from many various combinations of special characters.</p>
<p>Now I can just commit .md files to my repo and use any other better text editor in existence. Problem solved.</p>
<h2 id="no-regrets-i-still-had-fun-exploring-whats-out-there-and-even-this-is-still-overkill-i-really-dont-need-react-with-all-the-ssr-components-going-on-but-this-is-the-life-i-live"><a class="anchor" aria-hidden="true" tabindex="-1" href="#no-regrets-i-still-had-fun-exploring-whats-out-there-and-even-this-is-still-overkill-i-really-dont-need-react-with-all-the-ssr-components-going-on-but-this-is-the-life-i-live"><span class="icon icon-link"></span></a>No regrets I still had fun exploring whats out there and even this is still overkill, I really don't need react with all the SSR components going on, but this is the life I live.</h2>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lighthouse auditing with github actions for Vercel deployments]]></title>
            <description><![CDATA[Guide to setting up lighthouse ci to run on Vercel deployments through github actions]]></description>
            <link>https://www.jeremyharland.com/blog/lighthouse-cli-github-action</link>
            <guid isPermaLink="true">https://www.jeremyharland.com/blog/lighthouse-cli-github-action</guid>
            <category><![CDATA[lighthouse]]></category>
            <category><![CDATA[QA]]></category>
            <category><![CDATA[ci/cd]]></category>
            <dc:creator><![CDATA[Jeremy Harland]]></dc:creator>
            <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="/images/blog/lighthouse-action/lighthouse-icon.png" length="0" type="image/jpeg"/>
            <content:encoded><![CDATA[<p>This week I accidentally removed my <code>sitemap.xml</code> when configuring some pre-rendering settings which then led me to discover that I had not been serving a <code>robots.txt</code> page for a couple of months. This post goes through my steps to help prevent this from happening again.</p>
<hr>
<h1 id="lighthouse"><a class="anchor" aria-hidden="true" tabindex="-1" href="#lighthouse"><span class="icon icon-link"></span></a>Lighthouse</h1>
<p>Lighthouse is an auditing tool maintained by Google that has the capabilities to assess a site's performance, accessibility, best practices and Seo. Google's <a href="https://pagespeed.web.dev/">PageSpeed Insights</a> uses lighthouse under the hood to perform analysis on pages if you are familiar with that.</p>
<hr>
<h1 id="what-vercel-already-gives-you"><a class="anchor" aria-hidden="true" tabindex="-1" href="#what-vercel-already-gives-you"><span class="icon icon-link"></span></a>What Vercel already gives you</h1>
<p>Vercel has <a href="https://vercel.com/docs/speed-insights">Speed insights</a> which is easy to set up but is only limited to showing you how the user is performing when actively used. There seems to be a promoted integration with Vercel <a href="https://www.debugbear.com/docs">DebugBear</a> that can serve the same purpose. However on the day I did this, their docs were down so I did not proceed.</p>
<hr>
<h1 id="goals"><a class="anchor" aria-hidden="true" tabindex="-1" href="#goals"><span class="icon icon-link"></span></a>Goals</h1>
<ul>
<li>Lighthouse can be run locally for quick development (this can also be integrated with Chrome)</li>
<li>Tests are run on all branches pushed to Github</li>
<li>Production deploys are also subjected to testing</li>
</ul>
<hr>
<h1 id="setting-up-lighthouse"><a class="anchor" aria-hidden="true" tabindex="-1" href="#setting-up-lighthouse"><span class="icon icon-link"></span></a>Setting up Lighthouse</h1>
<h2 id="prerequisites"><a class="anchor" aria-hidden="true" tabindex="-1" href="#prerequisites"><span class="icon icon-link"></span></a>Prerequisites</h2>
<ul>
<li>Code is hosted on Github</li>
<li>Project is deployed to Vercel</li>
<li>Basic Github actions knowledge</li>
</ul>
<h2 id="installation-and-configuration"><a class="anchor" aria-hidden="true" tabindex="-1" href="#installation-and-configuration"><span class="icon icon-link"></span></a>Installation and configuration</h2>
<p>In your project run with your dedicated package manager</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">pnpm</span><span style="color:#032563;--shiki-dark:#9ECBFF"> add</span><span style="color:#023B95;--shiki-dark:#79B8FF"> -D</span><span style="color:#032563;--shiki-dark:#9ECBFF"> @lhci/cli</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">  </span></span></code></pre>
<p>Create a <code>lighthouserc.cjs</code> file in the root of your directory here we will need to consider that this will be getting read locally and from Github actions.</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="js"><code><span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> fs</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#622CBC;--shiki-dark:#B392F0"> require</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">'fs'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> isProduction</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  process.env.</span><span style="color:#023B95;--shiki-dark:#79B8FF">BASE_URL</span><span style="color:#A0111F;--shiki-dark:#F97583"> &#x26;&#x26;</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> process.env.</span><span style="color:#023B95;--shiki-dark:#79B8FF">BASE_URL</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">.</span><span style="color:#622CBC;--shiki-dark:#B392F0">includes</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">'jeremyharland.com'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> paths</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> [</span><span style="color:#032563;--shiki-dark:#9ECBFF">'/'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, </span><span style="color:#032563;--shiki-dark:#9ECBFF">'/pick'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, </span><span style="color:#032563;--shiki-dark:#9ECBFF">'/your'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, </span><span style="color:#032563;--shiki-dark:#9ECBFF">'/urls'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, </span><span style="color:#032563;--shiki-dark:#9ECBFF">'/here'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> urls</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> paths.</span><span style="color:#622CBC;--shiki-dark:#B392F0">map</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  (</span><span style="color:#702C00;--shiki-dark:#FFAB70">path</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">) </span><span style="color:#A0111F;--shiki-dark:#F97583">=></span><span style="color:#032563;--shiki-dark:#9ECBFF"> `${</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">process</span><span style="color:#032563;--shiki-dark:#9ECBFF">.</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">env</span><span style="color:#032563;--shiki-dark:#9ECBFF">.</span><span style="color:#023B95;--shiki-dark:#79B8FF">BASE_URL</span><span style="color:#A0111F;--shiki-dark:#F97583"> ||</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'http://localhost:3000'}${</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">path</span><span style="color:#032563;--shiki-dark:#9ECBFF">}`</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#023B95;--shiki-dark:#79B8FF">module</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">.</span><span style="color:#023B95;--shiki-dark:#79B8FF">exports</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  ci: {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    collect: {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      url: urls,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      numberOfRuns: isProduction </span><span style="color:#A0111F;--shiki-dark:#F97583">?</span><span style="color:#023B95;--shiki-dark:#79B8FF"> 3</span><span style="color:#A0111F;--shiki-dark:#F97583"> :</span><span style="color:#023B95;--shiki-dark:#79B8FF"> 1</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      settings: {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        preset: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'desktop'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        throttlingMethod: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'simulate'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        mobile: </span><span style="color:#023B95;--shiki-dark:#79B8FF">false</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        width: </span><span style="color:#023B95;--shiki-dark:#79B8FF">1350</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        height: </span><span style="color:#023B95;--shiki-dark:#79B8FF">940</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        deviceScaleFactor: </span><span style="color:#023B95;--shiki-dark:#79B8FF">1</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        chromeFlags: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'--headless --no-sandbox'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        blockedUrlPatterns: [</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          '*google-analytics*'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          '*googletagmanager*'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          '*hotjar*'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          '*segment*'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          '*sentry*'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        ],</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    upload: {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      target: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'temporary-public-storage'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Build your project and serve it locally, performance scores will be poor if you run the dev server instead of the built preview</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">pnpm</span><span style="color:#032563;--shiki-dark:#9ECBFF"> build</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="color:#702C00;--shiki-dark:#B392F0">pnpm</span><span style="color:#032563;--shiki-dark:#9ECBFF"> preview</span></span></code></pre>
<p>then on another console run the tests</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">pnpm</span><span style="color:#032563;--shiki-dark:#9ECBFF"> dlx</span><span style="color:#032563;--shiki-dark:#9ECBFF"> @lhci/cli</span><span style="color:#032563;--shiki-dark:#9ECBFF"> autorun</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> </span></span></code></pre>
<p>Once these have finished open the report and you should hopefully see something like this.</p>
<p><img src="/images/blog/lighthouse-action/local-results.jpg" alt="Local results"></p>
<h2 id="github-integration"><a class="anchor" aria-hidden="true" tabindex="-1" href="#github-integration"><span class="icon icon-link"></span></a>Github Integration</h2>
<h3 id="workflow-yaml-file"><a class="anchor" aria-hidden="true" tabindex="-1" href="#workflow-yaml-file"><span class="icon icon-link"></span></a>Workflow YAML file</h3>
<p>create a <code>lighthouse.yml</code> in <code>.github/workflows/</code> with the following content.</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="yml"><code><span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">name</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">Lighthouse CI</span></span>
<span class="line"></span>
<span class="line"><span style="color:#023B95;--shiki-dark:#79B8FF">on</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">  deployment_status</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">jobs</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">  lighthouse</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">    if</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">github.event.deployment_status.state == 'success'</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">    runs-on</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">ubuntu-latest</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">    steps</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      - </span><span style="color:#024C1A;--shiki-dark:#85E89D">name</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">Checkout</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        uses</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      - </span><span style="color:#024C1A;--shiki-dark:#85E89D">name</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">Install pnpm</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        uses</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">pnpm/action-setup@v4</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        with</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">          version</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#023B95;--shiki-dark:#79B8FF">9.15.4</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      - </span><span style="color:#024C1A;--shiki-dark:#85E89D">uses</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">actions/setup-node@v4</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        with</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">          node-version</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'24'</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">          cache</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">pnpm</span></span>
<span class="line"></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      - </span><span style="color:#024C1A;--shiki-dark:#85E89D">name</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">Set base URL</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        id</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">url</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        run</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#A0111F;--shiki-dark:#F97583">|</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          if [ "${{ github.event.deployment.environment }}" = "Production" ]; then</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">            echo "value=https://yourdomain.com" >> $GITHUB_OUTPUT</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          else</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">            echo "value=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      - </span><span style="color:#024C1A;--shiki-dark:#85E89D">name</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">Run Lighthouse CI</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        run</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">pnpm dlx @lhci/cli autorun</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">        env</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">          BASE_URL</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">${{ steps.url.outputs.value }}</span></span>
<span class="line"><span style="color:#024C1A;--shiki-dark:#85E89D">          LHCI_GITHUB_APP_TOKEN</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">${{ secrets.LHCI_GITHUB_APP_TOKEN }}</span></span></code></pre>
<h3 id="lighthouse-github-app"><a class="anchor" aria-hidden="true" tabindex="-1" href="#lighthouse-github-app"><span class="icon icon-link"></span></a>Lighthouse Github App</h3>
<p>Although not required, this makes your pr's look a bit nicer and creates a small summary of your actions run. Configure <a href="https://github.com/apps/lighthouse-ci">lighthouse ci</a> for your Github project.</p>
<figure>
  <img src="/images/blog/lighthouse-action/action-secrets.jpg" alt="Github Action Secrets">
  <figcaption>Github Action Secrets</figcaption>
</figure>
<p>Add <code>LHCI_GITHUB_APP_TOKEN</code> as a secret for your actions.</p>
<hr>
<h2 id="vercel-protection-rules"><a class="anchor" aria-hidden="true" tabindex="-1" href="#vercel-protection-rules"><span class="icon icon-link"></span></a>Vercel Protection rules</h2>
<p>If you had jumped the gun and pushed this all to Github you will have noticed that your preview runs would have failed due to lighthouse not being able to access your page.</p>
<p>In your Vercel project deployment protection settings create a bypass secret that we will add as headers to our lighthouse config.</p>
<p><img src="/images/blog/lighthouse-action/vercel-bypass.jpg" alt="bypass protection"></p>
<p>Add this to your github action secrets as <code>VERCEL_AUTOMATION_BYPASS_SECRET</code> and pass this into the autorun action in <code>lighthouse.yml</code>. Finally in the Lighthouse config file add this to the settings section.</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="js"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">extraHeaders</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">          'x-vercel-protection-bypass'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            process.env.</span><span style="color:#023B95;--shiki-dark:#79B8FF">VERCEL_AUTOMATION_BYPASS_SECRET</span><span style="color:#A0111F;--shiki-dark:#F97583"> ||</span><span style="color:#032563;--shiki-dark:#9ECBFF"> ''</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        },</span></span></code></pre>
<p>Now lighthouse will be able to successfully access preview branches and pushing to your repo should look like this now.</p>
<p><img src="/images/blog/lighthouse-action/github-run.jpg" alt="bypass protection"></p>
<hr>
<h2 id="assertions"><a class="anchor" aria-hidden="true" tabindex="-1" href="#assertions"><span class="icon icon-link"></span></a>Assertions</h2>
<p>Now you can add some assertions to your lighthouse config. The main thing to note here is that your SEO assertions will need to be off for previews as these pages are not indexed.</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="js"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">assert</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">      assertions</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#66707B;--shiki-dark:#6A737D">        // SEO specifics — skip on preview, these will always fail</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">        'meta-description'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: isProduction </span><span style="color:#A0111F;--shiki-dark:#F97583">?</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> [</span><span style="color:#032563;--shiki-dark:#9ECBFF">'warn'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, { minScore: </span><span style="color:#023B95;--shiki-dark:#79B8FF">1</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> }] </span><span style="color:#A0111F;--shiki-dark:#F97583">:</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'off'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">        'robots-txt'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: isProduction </span><span style="color:#A0111F;--shiki-dark:#F97583">?</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> [</span><span style="color:#032563;--shiki-dark:#9ECBFF">'warn'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, { minScore: </span><span style="color:#023B95;--shiki-dark:#79B8FF">1</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> }] </span><span style="color:#A0111F;--shiki-dark:#F97583">:</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'off'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">        canonical</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: isProduction </span><span style="color:#A0111F;--shiki-dark:#F97583">?</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> [</span><span style="color:#032563;--shiki-dark:#9ECBFF">'warn'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">, { minScore: </span><span style="color:#023B95;--shiki-dark:#79B8FF">1</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> }] </span><span style="color:#A0111F;--shiki-dark:#F97583">:</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'off'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<hr>
<h2 id="tips--future-iterations"><a class="anchor" aria-hidden="true" tabindex="-1" href="#tips--future-iterations"><span class="icon icon-link"></span></a>Tips &#x26; Future iterations</h2>
<p>For my project I created a script that pulls all my urls from <code>sitemap.xml</code> and writes them to a text file. This is executed within the lighthouse workflow.
Currently these only test for one type of screen view so there is room to extend that.</p>
<hr>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RSS feed with Tanstack Start]]></title>
            <description><![CDATA[How to generate an RSS to serve updates for your site with Tanstack Start]]></description>
            <link>https://www.jeremyharland.com/blog/rss-feed-tanstack-start</link>
            <guid isPermaLink="true">https://www.jeremyharland.com/blog/rss-feed-tanstack-start</guid>
            <category><![CDATA[tanstack]]></category>
            <category><![CDATA[rss]]></category>
            <category><![CDATA[webdev]]></category>
            <dc:creator><![CDATA[Jeremy Harland]]></dc:creator>
            <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="/images/blog/tanstack-start-rss.jpg" length="0" type="image/jpeg"/>
            <content:encoded><![CDATA[<p>I have been slowly working at this site more since <a href="https://tanstack.com/start/latest">Tanstack Start</a> has been in RC. This is my first time building with Tanstack Router, coming from a background primarily working on Nextjs with react query. Some conventions were slightly different along the way but mostly quite similar concepts.</p>
<hr>
<h1 id="tanstack-start-docs"><a class="anchor" aria-hidden="true" tabindex="-1" href="#tanstack-start-docs"><span class="icon icon-link"></span></a>Tanstack Start Docs</h1>
<p>The Tanstack Start documentation are great and comes with an <a href="https://tanstack.com/start/latest/docs/framework/react/guide/seo#dynamic-robotstxt">SEO guide</a> that outlines how to create <code>sitemaps.xml</code> and <code>robots.txt</code> files statically and dynamically. I had no need to create a dynamic robots.txt for this site and opted to just use Tanstack's built-in sitemap generator. Reading the guide on the the dynamic <code>robots.txt</code> would have shown exactly what to do for the <code>rss.xml</code> file. The docs also recommend reading the <a href="https://tanstack.com/router/latest">Tanstack Router</a> as they are coupled by design.</p>
<h1 id="generating-a-dynamic-rssxml-file"><a class="anchor" aria-hidden="true" tabindex="-1" href="#generating-a-dynamic-rssxml-file"><span class="icon icon-link"></span></a>Generating a dynamic rss.xml file</h1>
<h2 id="perquisites"><a class="anchor" aria-hidden="true" tabindex="-1" href="#perquisites"><span class="icon icon-link"></span></a>Perquisites</h2>
<p>This guide assumes you have a way to return all your posts/content and your tanstack start site is not statically deployed.</p>
<h2 id="server-route"><a class="anchor" aria-hidden="true" tabindex="-1" href="#server-route"><span class="icon icon-link"></span></a>Server Route</h2>
<p>Create a server route with the file <code>src/routes/rss[.]xml.ts</code>, this was not abundantly clear to me but is outlined in the <a href="https://tanstack.com/router/latest/docs/routing/file-naming-conventions">File Name Conventions</a> apart of the router documentation. Download the <a href="https://www.npmjs.com/package/rss">rss npm package</a> with</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#702C00;--shiki-dark:#B392F0">pnpm</span><span style="color:#032563;--shiki-dark:#9ECBFF"> install</span><span style="color:#032563;--shiki-dark:#9ECBFF"> rss</span></span></code></pre>
<p>or with whatever package manager you are using.</p>
<p>Then after configuring your posts you should end up with a file looking like</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">import</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { env } </span><span style="color:#A0111F;--shiki-dark:#F97583">from</span><span style="color:#032563;--shiki-dark:#9ECBFF"> '@/env'</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">import</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { createFileRoute } </span><span style="color:#A0111F;--shiki-dark:#F97583">from</span><span style="color:#032563;--shiki-dark:#9ECBFF"> '@tanstack/react-router'</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">import</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { allPosts } </span><span style="color:#A0111F;--shiki-dark:#F97583">from</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'content-collections'</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">import</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> RSS </span><span style="color:#A0111F;--shiki-dark:#F97583">from</span><span style="color:#032563;--shiki-dark:#9ECBFF"> 'rss'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">export</span><span style="color:#A0111F;--shiki-dark:#F97583"> const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> Route</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#622CBC;--shiki-dark:#B392F0"> createFileRoute</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">'/rss.xml'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">)({</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  server: {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    handlers: {</span></span>
<span class="line"><span style="color:#622CBC;--shiki-dark:#B392F0">      GET</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#A0111F;--shiki-dark:#F97583">async</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> () </span><span style="color:#A0111F;--shiki-dark:#F97583">=></span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">        const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> siteUrl</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> env.</span><span style="color:#023B95;--shiki-dark:#79B8FF">PUBLIC_BASE_URL</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">        const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> feed</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#A0111F;--shiki-dark:#F97583"> new</span><span style="color:#622CBC;--shiki-dark:#B392F0"> RSS</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          title: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'&#x3C;Your Title Here>'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          description:</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">            '&#x3C;Your description Here>'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          feed_url: </span><span style="color:#032563;--shiki-dark:#9ECBFF">`${</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">siteUrl</span><span style="color:#032563;--shiki-dark:#9ECBFF">}/rss.xml`</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          site_url: siteUrl,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          language: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'en'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          pubDate: </span><span style="color:#A0111F;--shiki-dark:#F97583">new</span><span style="color:#622CBC;--shiki-dark:#B392F0"> Date</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(),</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          copyright: </span><span style="color:#032563;--shiki-dark:#9ECBFF">`All rights unreserved ${</span><span style="color:#A0111F;--shiki-dark:#F97583">new</span><span style="color:#622CBC;--shiki-dark:#B392F0"> Date</span><span style="color:#032563;--shiki-dark:#9ECBFF">().</span><span style="color:#622CBC;--shiki-dark:#B392F0">getFullYear</span><span style="color:#032563;--shiki-dark:#9ECBFF">()</span><span style="color:#032563;--shiki-dark:#9ECBFF">}`</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        allPosts.</span><span style="color:#622CBC;--shiki-dark:#B392F0">map</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">((</span><span style="color:#702C00;--shiki-dark:#FFAB70">post</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">) </span><span style="color:#A0111F;--shiki-dark:#F97583">=></span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          feed.</span><span style="color:#622CBC;--shiki-dark:#B392F0">item</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">({</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            title: post.title,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            description: post.description,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            author: post.authors.</span><span style="color:#622CBC;--shiki-dark:#B392F0">join</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">', '</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">),</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            url: </span><span style="color:#032563;--shiki-dark:#9ECBFF">`${</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">siteUrl</span><span style="color:#032563;--shiki-dark:#9ECBFF">}/blog/${</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">post</span><span style="color:#032563;--shiki-dark:#9ECBFF">.</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">slug</span><span style="color:#032563;--shiki-dark:#9ECBFF">}`</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            date: post.published,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            custom_elements: [</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">              { </span><span style="color:#032563;--shiki-dark:#9ECBFF">'content:encoded'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: { _cdata: post.rendered.markup } },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            ],            </span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">            enclosure: post.headerImage</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">              ?</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">                  url: post.headerImage,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">                  type: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'image/jpeg'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">                }</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">              :</span><span style="color:#023B95;--shiki-dark:#79B8FF"> undefined</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          })</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        })</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">        return</span><span style="color:#A0111F;--shiki-dark:#F97583"> new</span><span style="color:#622CBC;--shiki-dark:#B392F0"> Response</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(feed.</span><span style="color:#622CBC;--shiki-dark:#B392F0">xml</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">({ indent: </span><span style="color:#023B95;--shiki-dark:#79B8FF">true</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> }), {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          status: </span><span style="color:#023B95;--shiki-dark:#79B8FF">200</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          headers: {</span></span>
<span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">            'Content-Type'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#032563;--shiki-dark:#9ECBFF">'text/xml'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">          },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">        })</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="configuration"><a class="anchor" aria-hidden="true" tabindex="-1" href="#configuration"><span class="icon icon-link"></span></a>Configuration</h3>
<ul>
<li>In your headers you can control how long your want to cache this for or not at all</li>
<li>There is more rss options you can configure or less! I like to add the image if it is present as most modern readers will render this in there ui.</li>
</ul>
<hr>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tanstack Server Components]]></title>
            <description><![CDATA[Tanstack Start React Server Components first thoughts and usage]]></description>
            <link>https://www.jeremyharland.com/blog/tanstack-react-server-components</link>
            <guid isPermaLink="true">https://www.jeremyharland.com/blog/tanstack-react-server-components</guid>
            <category><![CDATA[tanstack]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[rsc]]></category>
            <dc:creator><![CDATA[Jeremy Harland]]></dc:creator>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="/images/blog/tanstack-start-react-server-components.jpg" length="0" type="image/jpeg"/>
            <content:encoded><![CDATA[<p><a href="https://tanstack.com/blog/react-server-components">Tanstack Start</a> just released their implementation of React Server Components (RSC), the highly pushed feature by the <a href="https://nextjs.org/">Next.js</a> team, with quite a different approach to its application.</p>
<hr>
<h1 id="what-is-rsc"><a class="anchor" aria-hidden="true" tabindex="-1" href="#what-is-rsc"><span class="icon icon-link"></span></a>What is RSC</h1>
<p>RSC is the <a href="https://react.dev/reference/rsc/server-components">React feature</a> that allows the server to do the rendering for the client, leading to less JS that the client has to download.<br>
I won't debate on whether or not people require it, this site does not but I like to use this as a place to try new things.</p>
<h2 id="tanstack-vs-nextjs"><a class="anchor" aria-hidden="true" tabindex="-1" href="#tanstack-vs-nextjs"><span class="icon icon-link"></span></a>TanStack vs Next.js</h2>
<p>The team at TanStack have driven a client-first framework with the ability to opt into RSC where you see fit. Conversely, Next.js is RSC-first and then components are marked as <code>use client</code> when they are meant to be rendered on the client. This, by design, leads people to offload extra compute onto their server, which conveniently is likely to be Vercel.</p>
<p>At work I am dealing with a semi-large Next.js project with all the bells and whistles, and I come across this pattern that people seem to fall into:</p>
<p>Fetching data on the server then rendering some view component ✅</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">async</span><span style="color:#A0111F;--shiki-dark:#F97583"> function</span><span style="color:#622CBC;--shiki-dark:#B392F0"> FaqPage</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  let</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> data</span><span style="color:#A0111F;--shiki-dark:#F97583">:</span><span style="color:#702C00;--shiki-dark:#B392F0"> FaqContent</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  try</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">    data </span><span style="color:#A0111F;--shiki-dark:#F97583">=</span><span style="color:#A0111F;--shiki-dark:#F97583"> await</span><span style="color:#622CBC;--shiki-dark:#B392F0"> getFaqContent</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  } </span><span style="color:#A0111F;--shiki-dark:#F97583">catch</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> (e) {</span></span>
<span class="line"><span style="color:#622CBC;--shiki-dark:#B392F0">    redirect</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">'/error'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  return</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> &#x3C;</span><span style="color:#702C00;--shiki-dark:#B392F0">FaqPage</span><span style="color:#702C00;--shiki-dark:#B392F0"> data</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">={</span><span style="color:#702C00;--shiki-dark:#FFAB70">data</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">} />;</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">export</span><span style="color:#A0111F;--shiki-dark:#F97583"> default</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> FaqPage;</span></span></code></pre>
<p>So far so good! Then diving into the actual Faq page, I will often see the opening line:</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#032563;--shiki-dark:#9ECBFF">'use client'</span></span></code></pre>
<p>Hindering all the setup involved with the power of RSC, likely because there is a button somewhere on that page. The nature of the framework being server-first strips the significance of RSC, as it is not a conscious thought that devs make when building.</p>
<p>Whereas on the TanStack side, I had a go converting the bulk of my pages here into server components, as most of my pages have no client interaction whatsoever, so it makes sense.</p>
<p>Here is my blog page using RSC now</p>
<pre class="shiki shiki-themes github-light-high-contrast github-dark" style="background-color:#ffffff;--shiki-dark-bg:#24292e;color:#0e1116;--shiki-dark:#e1e4e8" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> getBlogPageContent</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#622CBC;--shiki-dark:#B392F0"> createServerFn</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">().</span><span style="color:#622CBC;--shiki-dark:#B392F0">handler</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#A0111F;--shiki-dark:#F97583">async</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> () </span><span style="color:#A0111F;--shiki-dark:#F97583">=></span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> Renderable</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#A0111F;--shiki-dark:#F97583"> await</span><span style="color:#622CBC;--shiki-dark:#B392F0"> renderServerComponent</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(&#x3C;</span><span style="color:#702C00;--shiki-dark:#B392F0">Blog</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> />)</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  return</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { Renderable }</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">export</span><span style="color:#A0111F;--shiki-dark:#F97583"> const</span><span style="color:#023B95;--shiki-dark:#79B8FF"> Route</span><span style="color:#A0111F;--shiki-dark:#F97583"> =</span><span style="color:#622CBC;--shiki-dark:#B392F0"> createFileRoute</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">(</span><span style="color:#032563;--shiki-dark:#9ECBFF">'/_layout/blog/'</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">)({</span></span>
<span class="line"><span style="color:#622CBC;--shiki-dark:#B392F0">  loader</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">: </span><span style="color:#A0111F;--shiki-dark:#F97583">async</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> () </span><span style="color:#A0111F;--shiki-dark:#F97583">=></span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">    const</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { </span><span style="color:#023B95;--shiki-dark:#79B8FF">Renderable</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> } </span><span style="color:#A0111F;--shiki-dark:#F97583">=</span><span style="color:#A0111F;--shiki-dark:#F97583"> await</span><span style="color:#622CBC;--shiki-dark:#B392F0"> getBlogPageContent</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">    return</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { BlogPageContent: Renderable }</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">  component: BlogPage,</span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">function</span><span style="color:#622CBC;--shiki-dark:#B392F0"> BlogPage</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  const</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> { </span><span style="color:#023B95;--shiki-dark:#79B8FF">BlogPageContent</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> } </span><span style="color:#A0111F;--shiki-dark:#F97583">=</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> Route.</span><span style="color:#622CBC;--shiki-dark:#B392F0">useLoaderData</span><span style="color:#0E1116;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#A0111F;--shiki-dark:#F97583">  return</span><span style="color:#0E1116;--shiki-dark:#E1E4E8"> &#x3C;>{BlogPageContent}</span><span style="color:#A0111F;--shiki-dark:#F97583">&#x3C;/></span></span>
<span class="line"><span style="color:#0E1116;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Everything still reads like it did before, but it just feels more deliberate that I am loading things on the server for a purpose, and it is not just magic happening under the framework's hood. I am yet to do a more complex page that requires both client and server components, which will be a fairer test - but for now I'll just stick to drinking the TanStack Kool-Aid!</p>
<hr>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is this?]]></title>
            <description><![CDATA[What is this? Where is this going? Why am I doing this? Who asked]]></description>
            <link>https://www.jeremyharland.com/blog/what-is-this</link>
            <guid isPermaLink="true">https://www.jeremyharland.com/blog/what-is-this</guid>
            <dc:creator><![CDATA[Jeremy Harland]]></dc:creator>
            <pubDate>Wed, 20 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="/images/blog/what-is-this.jpg" length="0" type="image/jpeg"/>
            <content:encoded><![CDATA[<h1 id="motivation"><a class="anchor" aria-hidden="true" tabindex="-1" href="#motivation"><span class="icon icon-link"></span></a>Motivation</h1>
<p>I set out to do this initially as a means of improving my general writing skills. I am rarely writing nowadays and did a degree that also didn't demand writing!</p>
<p>Also have been wanting to hone in on my skills of using markdown, I want to enjoy notion but I despise having to do a "/" to initiate anything.</p>
<p>ps this editor is so scat I cannot do a less then sign without the whole thing crashing.</p>
<hr>]]></content:encoded>
        </item>
    </channel>
</rss>