<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: nuro</title>
    <description>The latest articles on Forem by nuro (@nuro).</description>
    <link>https://forem.com/nuro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F23712%2Ffc3de528-2e01-4f88-959d-f99c7bd2fac9.png</url>
      <title>Forem: nuro</title>
      <link>https://forem.com/nuro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nuro"/>
    <language>en</language>
    <item>
      <title>Cloudflare is almost perfect</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Thu, 16 Jan 2025 22:41:34 +0000</pubDate>
      <link>https://forem.com/nuro/cloudflare-is-almost-perfect-1445</link>
      <guid>https://forem.com/nuro/cloudflare-is-almost-perfect-1445</guid>
      <description>&lt;p&gt;If you're building a platform or product nowadays, as a developer in some way or another Cloudflare may fit into your stack. Whether that be from just using their network for DDoS protection, using R2 for cheaper object storage, or even going all in on using their Workers product for serverless compute.&lt;/p&gt;

&lt;p&gt;However, having poked around with Cloudflare for a while now, there are a few things that I think are missing from their platform that would make it a perfect platform for developers.&lt;/p&gt;

&lt;p&gt;So, since we're close to the end of the year I thought I may as well make a little wish list of things I'd like to see from Cloudflare in 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containers
&lt;/h2&gt;

&lt;p&gt;Starting off with my biggest ask: &lt;strong&gt;&lt;em&gt;Containers, containers and more containers!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used to be a big fan of serverless computing or lambdas, and I still think they have their place. But I have seen too many horror stories of insane bills &amp;amp; issues myself of having to work around their limitations that if I was to build a product today I would not even bother with serverless lambdas at all and instead containerize everything.&lt;/p&gt;

&lt;p&gt;With a container pretty much all the common problems go out the window, especially those stupid cold start issues.&lt;/p&gt;

&lt;p&gt;The problem right now though is most other container-as-a-service products are either too expensive or too limited in functionality. Google Cloud Run is the closest to what I would want, but it is both too expensive to run and also locks you into the Google Cloud ecosystem which is both complex and expensive in every other way.&lt;/p&gt;

&lt;p&gt;Now, Cloudflare did already &lt;a href="https://blog.cloudflare.com/container-platform-preview/" rel="noopener noreferrer"&gt;announce earlier this year&lt;/a&gt; that they are already working on their own container-as-a-service product, and is apparently already in use in a number of other Cloudflare products. So I am really looking forward to seeing this product come out of closed alpha/beta and into general availability for everyone to use.&lt;/p&gt;

&lt;p&gt;But with that said, I want to emphasize a few key features I really hope Cloudflare considers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global deployments&lt;/strong&gt;: Workers are already deployed globally to multiple regions, let containers do the same. Let me just check a box to add a region &amp;amp; leave Cloudflare to handle the networking and geo-routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum scale count&lt;/strong&gt;: 🖕 cold starts. Let me just set a minimum container / pod count so I can have at least 1 container always running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidecar containers&lt;/strong&gt;: Google Cloud Run has a cool feature where &lt;a href="https://cloud.google.com/blog/products/serverless/cloud-run-now-supports-multi-container-deployments" rel="noopener noreferrer"&gt;you can run multiple containers next to each other&lt;/a&gt;. So for example, if you want to run &lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt; or &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt; as a reverse proxy for your ingress container and then have both your web frontend container &amp;amp; backend api container co-located in the same service, you can do that &amp;amp; have everything be super low latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image registry&lt;/strong&gt;: Offer a 1st party image registry &amp;amp; make it easy to use. If not, make supporting the GitHub Container Registry the go-to. For me I don't want to bother with Docker Hub when GitHub Container Registry does everything I need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 volume mounts&lt;/strong&gt;: Another cool feature Google Cloud Run offers is you can access a Google Cloud Storage (S3) bucket via file-system rather than over the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workers HTTP imports
&lt;/h2&gt;

&lt;p&gt;A while ago I made &lt;a href="https://bsky.app/profile/nuro.dev/post/3lafdjhzc3r2j" rel="noopener noreferrer"&gt;this post on Bluesky&lt;/a&gt; about how if you want to quickly deploy a web application, whether that be a RESTful API or a server-side rendered application, Deno Deploy with &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; is an absolutely killer combination. Especially since you can do it all via a web based editor.&lt;/p&gt;

&lt;p&gt;Being able to do something similar to this with Cloudflare Workers would be amazing. However right now there is one big blocker, besides TypeScript support, and that is that there is no native support for HTTP imports.&lt;/p&gt;

&lt;p&gt;Imagine to create a super basic RESTful API all you needed to do was go to the Cloudflare dashboard, go to the workers page, click "Create" &amp;amp; then paste in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _worker.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://esm.sh/hono@4.6.11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more need to set up a new Node.js project, repository to store it all, connect the repo to a CI/CD pipeline, etc. Just write the code and deploy it using your favourite packages via HTTP imports.&lt;/p&gt;

&lt;h2&gt;
  
  
  R2 feature compatibility
&lt;/h2&gt;

&lt;p&gt;R2 is amazing! Plain and simple object storage. You just give it data to store and that's it.&lt;/p&gt;

&lt;p&gt;But that being said, there is a few small missing features that I really hope get added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt;: For the rare use case it would be handy to be able to securely store data at rest, kind of like how Google Cloud Storage does it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revision history&lt;/strong&gt;: Being able to see the history of a file and restore it to a previous version. This would be super helpful for infrastructure as code files like Terraform state files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 client&lt;/strong&gt;: &lt;code&gt;@aws-sdk/client-s3&lt;/code&gt; sucks, both in terms of DX &amp;amp; package size. I wish Cloudflare would make &lt;a href="https://developers.cloudflare.com/r2/examples/demo-worker/" rel="noopener noreferrer"&gt;their R2 worker API&lt;/a&gt; a general purpose package for anyone to use. The closest I have found that is good in the meantime is &lt;a href="https://github.com/bradenmacdonald/s3-lite-client" rel="noopener noreferrer"&gt;&lt;code&gt;@bradenmacdonald/s3-lite-client&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The dashboard sucks
&lt;/h2&gt;

&lt;p&gt;I'm sorry but I will put it blunt, &lt;strong&gt;&lt;em&gt;it does suck.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's slow, confusing UX where products are placed sparatically, fast page loads but data loads in slowly filling the page with loading spinners &amp;amp; often giant red banners at the bottom telling me about some internal error when trying to load the page.&lt;/p&gt;

&lt;p&gt;This isn't as much of an issue if you're using any kind of infrastructure-as-a-code system, but even then you would still need to use the dashboard to view analytics, billing, etc, and on that scenario it would be helpful if the dashboard was a bit more user friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support more TLD's
&lt;/h2&gt;

&lt;p&gt;Over the past year or so I have slowly been migrating all my domains to Cloudflare but a lot of which I have had to wait for a little bit for some missing top-level domains to be added to Cloudflare's registrar.&lt;/p&gt;

&lt;p&gt;Pretty much all of the essential TLD's have been added now, but I do hope Cloudflare continues to add more, like &lt;code&gt;.rs&lt;/code&gt; for Rust projects`, so that in future I don't even need to consider NameCheap or some other 3rd party TLD provider and instead I can just buy and consolidate everything in one place. Not exactly a sore spot but a nice-to-have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharable API key permissions
&lt;/h2&gt;

&lt;p&gt;This is a small one, but I think it would be really helpful for a niche number of developers and blog writers.&lt;/p&gt;

&lt;p&gt;In short, I wish that when you were creating a new API token in the Cloudflare dashboard that it would give you the option to copy a code, likely a hash of some kind, that you could then share around to paste in the exact permissions needed for something.&lt;/p&gt;

&lt;p&gt;For example: Say you're writing a blog post on how to use deploy a Cloudflare Pages site using Terraform. Terraform needs a Cloudflare API token to do this, but you don't want to give it full access to your account. So you create a new API token with the exact permissions needed, copy the hash, and then paste it into your blog post.&lt;/p&gt;

&lt;p&gt;Very niche but handy if there is a lot of permissions that need to be added or say permissions get changed or removed, the hash could automatically handle populating the replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics site permissions role
&lt;/h2&gt;

&lt;p&gt;Lastly this sort of follows on from the above, but currently from my testing there is no way to create an API token that has write access for Cloudflare analytics sites so there is no way to automate the creation of new analytics sites with something like Terraform. Or if there is a permission that can do it, clearly document it instead. Currently there is a "Account Analytics" or "Analytics" permission but both seem to be read only.&lt;/p&gt;




&lt;p&gt;And that's it, for now anyway. I am sure over the course of the next year or so I will think of or find many other little features or fixes that Cloudflare needs to implement to finally make their platform over that line where I would use Cloudflare's suite of products for everything I need to build a product.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>How to build an Astro collection loader</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Thu, 16 Jan 2025 22:39:43 +0000</pubDate>
      <link>https://forem.com/nuro/how-to-build-an-astro-collection-loader-3no1</link>
      <guid>https://forem.com/nuro/how-to-build-an-astro-collection-loader-3no1</guid>
      <description>&lt;p&gt;A few days ago &lt;a href="https://astro.build/blog/astro-4140/#experimental-content-layer-api" rel="noopener noreferrer"&gt;Astro released version 1.14&lt;/a&gt; which introduced a new feature called the "Content Layer API". This feature builds on the existing content collections feature and instead expands it so you can use data from sources other than local files within the Astro project repository.&lt;/p&gt;

&lt;p&gt;Now, to get started the Astro team has already published a number of loaders to deal with the most common possible cases you may need. Including "feeds" like RSS feeds, CSV files, etc. But what if you wanted to build your own? Well it's actually quite simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Astro
&lt;/h2&gt;

&lt;p&gt;First things first, you need to set up a new Astro project. You can do this by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will walk you through a very fancy CLI guide to set up your project.&lt;/p&gt;

&lt;p&gt;Once you have done that, you can run the following command to start your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this up and running you can visit &lt;code&gt;http://localhost:4321&lt;/code&gt; to see your new Astro project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable the experiment
&lt;/h2&gt;

&lt;p&gt;Next, you will need to make one slight tweak to the Astro configuration file to enable the Content Layer API as it's currently still experimental.&lt;/p&gt;

&lt;p&gt;To do this, open the &lt;code&gt;astro.config.mjs&lt;/code&gt; file and add a &lt;code&gt;experimental&lt;/code&gt; object with a &lt;code&gt;contentLayer&lt;/code&gt; key set to &lt;code&gt;true&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// astro.config.mjs &lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://astro.build/config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;contentLayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The basic structure
&lt;/h2&gt;

&lt;p&gt;With the experimental API enabled we can now actually start to build out the basic structure for a collection loader.&lt;/p&gt;

&lt;p&gt;Now, for the sake of example I'll be using TypeScript but it's not strictly required.&lt;/p&gt;

&lt;p&gt;First up, create a new file in your project. I'll call mine &lt;code&gt;src/loaders/jokes.ts&lt;/code&gt;. This file will be responsible for loading in a collection of posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/loaders/jokes.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/loaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokesLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At its most basic premise a loader is an object with a 2 required properties &amp;amp; one optional property:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; (&lt;strong&gt;Required&lt;/strong&gt;): This will be the name of the loader and how it will show up in logs and such.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;load&lt;/code&gt; (&lt;strong&gt;Required&lt;/strong&gt;): Here is the actual "loader logic" function where you actually fetch the data from whatever source you need. This function exposes a loader context parameter which can be used to access things like the &lt;code&gt;store&lt;/code&gt; where the data will actually get stored, &lt;code&gt;logger&lt;/code&gt; for logging, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema&lt;/code&gt; (&lt;strong&gt;Optional&lt;/strong&gt;): If you want to provide a Zod schema to validate the data after it's been fetched you can do so here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fetch some data
&lt;/h2&gt;

&lt;p&gt;Now we need some data, let's use &lt;a href="https://icanhazdadjoke.com/" rel="noopener noreferrer"&gt;this dad joke API&lt;/a&gt; to get a bunch of random dad jokes.&lt;/p&gt;

&lt;p&gt;To do this in the &lt;code&gt;load&lt;/code&gt; function all we need to do is make a &lt;code&gt;fetch&lt;/code&gt; request. For this API we just need to make sure we set the &lt;code&gt;Accept&lt;/code&gt; header to &lt;code&gt;application/json&lt;/code&gt; to get the data in JSON format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/loaders/jokes.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/loaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokesLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://icanhazdadjoke.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've added a &lt;code&gt;console.log&lt;/code&gt; statement for now just so we can check it works. But how do we do that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the loader
&lt;/h2&gt;

&lt;p&gt;With some actual logic in the loader we can hook it up to a collection to see what data we get back from the API.&lt;/p&gt;

&lt;p&gt;To do this create the following file &lt;code&gt;src/content/config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/content/config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jokesLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../loaders/jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jokesLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;jokes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we should now have a new collection called &lt;code&gt;jokes&lt;/code&gt; that we can access in our Astro project.&lt;/p&gt;

&lt;p&gt;If we run a build of the project with &lt;code&gt;npm run build&lt;/code&gt; we should see the loader being run and the data being logged to the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;21:32:37 &lt;span class="o"&gt;[&lt;/span&gt;content] Syncing content
21:32:37 &lt;span class="o"&gt;[&lt;/span&gt;jokes] &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:&lt;span class="s2"&gt;"mWS7hVKRSnb"&lt;/span&gt;,&lt;span class="s2"&gt;"joke"&lt;/span&gt;:&lt;span class="s2"&gt;"Past, present, and future walked into a bar.... It was tense."&lt;/span&gt;,&lt;span class="s2"&gt;"status"&lt;/span&gt;:200&lt;span class="o"&gt;}&lt;/span&gt;
21:32:37 &lt;span class="o"&gt;[&lt;/span&gt;content] Synced content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect, we can see our loader ran by the &lt;code&gt;[jokes]&lt;/code&gt; log message and the data being returned from the API. Additionally it confirms the data structure we get back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store the data
&lt;/h2&gt;

&lt;p&gt;Now we have some data we need to actually store it so we can access it in our Astro project.&lt;/p&gt;

&lt;p&gt;To do this we can use the &lt;code&gt;store&lt;/code&gt; scoped data store that is available on the &lt;code&gt;context&lt;/code&gt; parameter. This &lt;code&gt;ScopedDataStore&lt;/code&gt; appears to be some form of a superset of a regular &lt;code&gt;Map&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;When actually setting the data you need to provide a unique &lt;code&gt;id&lt;/code&gt; for the data, along with the data itself. This works quite well for us here as the API gives us an &lt;code&gt;id&lt;/code&gt; field for each joke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/loaders/jokes.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/loaders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jokesLoader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jokes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://icanhazdadjoke.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessing the data
&lt;/h2&gt;

&lt;p&gt;Since we removed the logging builds will still succeed if you run &lt;code&gt;npm run build&lt;/code&gt; but we won't see the data being logged anymore.&lt;/p&gt;

&lt;p&gt;To actually access the data, you can access it like you would any other collection in Astro.&lt;/p&gt;

&lt;p&gt;For example, in an Astro file you can use the &lt;code&gt;getCollection&lt;/code&gt; function to get the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/pages/index.astro --&amp;gt;&lt;/span&gt;
---
import { getCollection } from 'astro:content';

const jokes = await getCollection('jokes');
console.log('jokes', jokes);
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;npm run build&lt;/code&gt; with the above added you should see something along the lines of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; generating static routes
22:05:09 ▶ src/pages/index.astro
22:05:09   └─ /index.htmljokes &lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;: &lt;span class="s1"&gt;'9prWnjyImyd'&lt;/span&gt;,
    data: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;id&lt;/span&gt;: &lt;span class="s1"&gt;'9prWnjyImyd'&lt;/span&gt;,
      joke: &lt;span class="s1"&gt;'Why do bears have hairy coats? Fur protection.'&lt;/span&gt;,
      status: 200
    &lt;span class="o"&gt;}&lt;/span&gt;,
    collection: &lt;span class="s1"&gt;'jokes'&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="o"&gt;(&lt;/span&gt;+6ms&lt;span class="o"&gt;)&lt;/span&gt;
22:05:09 ✓ Completed &lt;span class="k"&gt;in &lt;/span&gt;9ms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that it works! Now every time you build your Astro project it will fetch a new dad joke from the API and store it in the collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that's it! You've now built your own collection loader for Astro. This is a very basic example but it should give you a good starting point to build more complex loaders.&lt;/p&gt;

&lt;p&gt;Later down the line this can be easily adapted into a function to return a loader so you could say take in some user options and publish it as a package for others to use.&lt;/p&gt;

&lt;p&gt;I'm excited to see what other loaders people come up with now and in the future.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>astro</category>
    </item>
    <item>
      <title>How to use Astro with Hono</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Thu, 16 Jan 2025 22:35:54 +0000</pubDate>
      <link>https://forem.com/nuro/how-to-use-astro-with-hono-3hlm</link>
      <guid>https://forem.com/nuro/how-to-use-astro-with-hono-3hlm</guid>
      <description>&lt;p&gt;Astro is the future of web frameworks. At least for me it is. It's been so nice to use &amp;amp; feels like the do it all framework to the point I want to use it for everything and anything.&lt;/p&gt;

&lt;p&gt;However, that being said, for building APIs I am yet to find something as nice to use as &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt;. It's simple, runs anywhere &amp;amp; has a very basic but usable RPC system similar to &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, but with less slow types.&lt;/p&gt;

&lt;p&gt;So, with that, why not learn how to combine the 2 so you can have the best of both worlds?&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Astro
&lt;/h2&gt;

&lt;p&gt;First things first, you need to set up a new Astro project. You can do this by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will walk you through a very fancy CLI guide to set up your project.&lt;/p&gt;

&lt;p&gt;Once you have done that, you can run the following command to start your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this up and running you can visit &lt;code&gt;http://localhost:4321&lt;/code&gt; to see your new Astro project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Hono
&lt;/h2&gt;

&lt;p&gt;Next, you will of course need to set up a basic &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; app.&lt;/p&gt;

&lt;p&gt;To get started you will need to install Hono as a dependency. This can be done with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;hono
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Hono installed you'll need to create a new endpoint in Astro to point all requests to a Hono instance.&lt;/p&gt;

&lt;p&gt;To do this create a new catch-all route in your &lt;code&gt;src/pages/&lt;/code&gt; directory. Something like: &lt;code&gt;src/pages/api/[...path].ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With this file you can create a new basic Hono instance like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/api/[...path].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make note of the &lt;code&gt;.basePath(...)&lt;/code&gt; part here, it's important. This tells Hono that all incoming requests should be prefixed with this path. In our case since we're using Astro, this will need to match the file structure of your Astro project. For example if your endpoint is in &lt;code&gt;src/pages/foo/bar/[...path].ts&lt;/code&gt; then you should use &lt;code&gt;/foo/bar/&lt;/code&gt; as the base path.&lt;/p&gt;

&lt;p&gt;Now, last up to finish setting up Hono we'll add a basic endpoint to retrieve some mock data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/api/[...path].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Goodbye World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Binding Hono to Astro
&lt;/h2&gt;

&lt;p&gt;With a basic Hono instance set up, we can now bind this to Astro.&lt;/p&gt;

&lt;p&gt;Astro normally expects a function to be exported with the name matching the HTTP method it is expected to handle. Such as &lt;code&gt;GET&lt;/code&gt; will handle &lt;code&gt;GET&lt;/code&gt; requests, &lt;code&gt;POST&lt;/code&gt; will handle &lt;code&gt;POST&lt;/code&gt; requests etc.&lt;/p&gt;

&lt;p&gt;Now, in our case we need to redirect all requests to Hono. This can be done by creating a new &lt;code&gt;ALL&lt;/code&gt; export. So let's create a basic one of these that just returns some text for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/api/[...path].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up we can then access the &lt;code&gt;context&lt;/code&gt; function parameter to access the incoming request &amp;amp; pass it to Hono's &lt;code&gt;fetch&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/api/[...path].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that you can now visit &lt;code&gt;http://localhost:4321/api/posts&lt;/code&gt; to see your mock data being returned from Hono.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a typed RPC client
&lt;/h2&gt;

&lt;p&gt;Now, for the final part of getting this whole thing working seemlessly. We can make it so when we want to make requests to this API you get typed request options like query parameters, headers etc, along with a fully typed response.&lt;/p&gt;

&lt;p&gt;You can read more about how this all works &lt;a href="https://hono.dev/guides/rpc" rel="noopener noreferrer"&gt;in the Hono documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To set this up is faily easy as all you will need to do export the type of your Hono instance &amp;amp; pass it to the special &lt;code&gt;fetch&lt;/code&gt; client that Hono offers called &lt;code&gt;hc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First up, export the type of your Hono instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/api/[...path].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, to actually use this type we can create a basic Astro page with a client-side script to make a fetch request using the Hono client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/pages/index.astro --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./api/[...path].ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { posts: [...] }&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And you're done! You now have a basic Astro project set up using Hono as your API. With the added bonus of being able to make typed requests &amp;amp; get fully-typed responses.&lt;/p&gt;

&lt;p&gt;I encourage more people to experiment &amp;amp; try out Hono with more projects because it is a truly amazing HTTP framework that can do so much more than many people realise. So much so that I am using it for all of my backend related projects &amp;amp; plan on creating some more posts on using it in future.&lt;/p&gt;

&lt;p&gt;For now though, keep building &amp;amp; have fun!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>astro</category>
    </item>
    <item>
      <title>Using GitHub as a CMS</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Wed, 19 Jan 2022 12:35:13 +0000</pubDate>
      <link>https://forem.com/nuro/using-github-as-a-cms-2n28</link>
      <guid>https://forem.com/nuro/using-github-as-a-cms-2n28</guid>
      <description>&lt;p&gt;Since re-launching my website with an all new redesign I added a number of cool features. One such feature I thought worth sharing about was my &lt;a href="https://nuro.dev/projects"&gt;portfolio page&lt;/a&gt; as it actually uses &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; as a CMS (Content Management System).&lt;/p&gt;

&lt;p&gt;So, let's go through and breakdown why I added this feature, why not use some other service &amp;amp; of course how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Why
&lt;/h2&gt;

&lt;p&gt;When re-designing my website I set out with a number of goals. One such goal was to retain a similar portfolio page to my existing website at the time which fetched all my GitHub repositories &amp;amp; just listed them all.&lt;/p&gt;

&lt;p&gt;However, I wanted to improve on this page by adding some sort of curation to it. Preferably via some method that is quick, easy to use, can be accessed from almost anywhere &amp;amp; preferably free.&lt;/p&gt;

&lt;h2&gt;
  
  
  ☁️ Headless CMS
&lt;/h2&gt;

&lt;p&gt;I knew I needed some kind of headless CMS in some form to let me curate the content I wanted displayed, so I did some thinking &amp;amp; some research.&lt;/p&gt;

&lt;p&gt;I found most people were happy to recommend other headless CMS services like &lt;a href="https://strapi.io/"&gt;Strapi&lt;/a&gt;, &lt;a href="https://www.sanity.io/"&gt;Sanity&lt;/a&gt;, &lt;a href="https://graphcms.com/"&gt;GraphCMS&lt;/a&gt;, etc which did seem to do the job I wanted of providing a platform for me to curate &amp;amp; manage my content without having to redeploy. But most of them had the same issues that I didn't like.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resilience
&lt;/h4&gt;

&lt;p&gt;Is it overkill to try &amp;amp; have your personal portfolio website to have a 99.999% uptime? Probably.&lt;/p&gt;

&lt;p&gt;Does it matter if my website goes down? No. But I like to try &amp;amp; treat as any other production environment &amp;amp; where possible not add another possible point of failure. In this case being a 3rd party service to serve the data for my portfolio page.&lt;/p&gt;

&lt;p&gt;If the 3rd party service goes down will my content somehow still be magically served by some cache somewhere until the service comes back online? Or is there just going to be no content served at all?&lt;/p&gt;

&lt;p&gt;"But you're fetching from GitHub, right? So you're still reliant on data from them" Fair point, yes I am. However, I am both more likely to trust GitHub's uptime &amp;amp; platform resilience than a CMS service, and it's easier to add in a fail safe so if GitHub does go down we can still serve content without them (More on this later).&lt;/p&gt;

&lt;h4&gt;
  
  
  Price
&lt;/h4&gt;

&lt;p&gt;Some may call me cheap for this but another goal for my personal website is to be as cheap as possible. As of writing my website costs me $20/year to run. That's it.&lt;/p&gt;

&lt;p&gt;How? Simple. $20/year for a domain, code hosted GitHub &amp;amp; deployed to &lt;a href="https://vercel.com/home?utm_source=nuro&amp;amp;utm_campaign=oss"&gt;Vercel&lt;/a&gt;. It's that cheap &amp;amp; I want to keep it that way.&lt;/p&gt;

&lt;p&gt;So, the idea of paying for another service, however nice a lot of them look, is just not for me. So I wanted to find an alternative that would serve the same function I need it to but for zero additional cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔨 How
&lt;/h2&gt;

&lt;p&gt;If you'd rather just look at the final code to see how it works, go check the &lt;a href="https://github.com/nurodev/nuro.dev/blob/667b055043b94d894172085fee794c3849a3f25f/lib/projects.ts"&gt;&lt;code&gt;projects.ts&lt;/code&gt;&lt;/a&gt; file in my websites GitHub repository.&lt;/p&gt;

&lt;p&gt;The short version of how it works is this: Using &lt;a href="https://github.com/topics"&gt;topics on GitHub&lt;/a&gt; you can filter what content you do &amp;amp; don't want to show. In my case I have given all my chosen repositories a &lt;code&gt;portfolio&lt;/code&gt; topic &amp;amp; that will make the repository show up on my portfolio page.&lt;/p&gt;

&lt;p&gt;To get this working there are 2 key things we're going to need: &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; &amp;amp; the &lt;a href="https://docs.github.com"&gt;GitHub api&lt;/a&gt;. Optionally you can also use some kind of cache, like &lt;a href="https://upstash.com/"&gt;Upstash&lt;/a&gt;, to cache your data &amp;amp; save on requests to the GitHub api.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching repositories
&lt;/h3&gt;

&lt;p&gt;Starting off we will set up a base &lt;code&gt;getServerSideProps&lt;/code&gt; function that will allow us to fetch the repository data on the server &amp;amp; pass the data to the client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/users/GITHUB_USERNAME/repos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Repository filtering
&lt;/h3&gt;

&lt;p&gt;Here is where we can add our own custom logic to curate the data based on the data returned from GitHub. In this example we'll be filtering out all the repositories that both don't have the &lt;code&gt;portfolio&lt;/code&gt; topic &amp;amp; are not archived.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archived&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GitHub Authentication
&lt;/h4&gt;

&lt;p&gt;Next up is an optional step that I highly recommend to reduce the chance of hitting the GitHub api rate limit.&lt;/p&gt;

&lt;p&gt;To complete this step you will first need to create a GitHub personal access token. You can follow the &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token"&gt;official guide here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With your newly generated access token, add it to your &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_PAT=abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With it added you can update your fetch request to add the token to your request headers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/users/GITHUB_USERNAME/repos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_PAT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Caching
&lt;/h4&gt;

&lt;p&gt;This last step is also optional but can help with platform resilience &amp;amp; response times. In short we'll use some form of super fast caching database, like Redis, to store the request data after the first request &amp;amp; from then on after serve the cached data instead of having to make multiple requests.&lt;/p&gt;

&lt;p&gt;For my website I ended up using &lt;a href="//upstash.com"&gt;Upstash&lt;/a&gt; which offers a single single Redis database for free. I recommend following their guide to &lt;a href="https://docs.upstash.com/redis"&gt;get started setting up your first Redis database&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With your database set up you should now have a &lt;code&gt;REDIS_URL&lt;/code&gt; to add your &lt;code&gt;.env&lt;/code&gt; file. It should look something like this &lt;code&gt;rediss://USERNAME:PASSWORD@HOST:PORT&lt;/code&gt;. Don't worry if there isn't a username.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_PAT=abc123
REDIS_URL=rediss://USERNAME:PASSWORD@HOST:PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a database to access we need a client to access it with. There's a number of Redis client's, including Upstash's &lt;a href="https://www.npmjs.com/package/@upstash/redis"&gt;own client&lt;/a&gt;, but I personally prefer &lt;code&gt;ioredis&lt;/code&gt; so that's what I will be using for this example but any others should work fine. Install it as a development dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; ioredis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using Yarn&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; ioredis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With it now installed we can create a new client instance to connect to our database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we can add the actual logic to check whether the cache already has the data we need &amp;amp; if not we can fetch the data fresh from GitHub &amp;amp; store it in the cache before we finally return it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repositories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repositories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✅ Conclusion
&lt;/h2&gt;

&lt;p&gt;And that's it, with all of that we have a very simple but resilient system that allows us to use GitHub to manage what content is displayed on our site, but also is not entirely reliant on GitHub so if there is any downtime for whatever reason on GitHub's end we still have the data cached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getServerSideProps&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repositories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/users/GITHUB_USERNAME/repos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_PAT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archived&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repositories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repositories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nextjs</category>
      <category>github</category>
      <category>cms</category>
      <category>api</category>
    </item>
    <item>
      <title>Improving the Next.js config</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Thu, 23 Dec 2021 00:35:15 +0000</pubDate>
      <link>https://forem.com/nuro/improving-the-nextjs-config-400j</link>
      <guid>https://forem.com/nuro/improving-the-nextjs-config-400j</guid>
      <description>&lt;p&gt;For the past few years I have been an avid lover of &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; and in the past year or so been using it in combination with &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;, an all new build tool that is just out of this world fast ⚡.&lt;br&gt;
However, spending more time working in the industry I have learned to fall back in love with &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; and more specifically &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, since switching back to using Next.js after so many years, I have noticed a number of things that have made me want to go back to using Vue + Vite.&lt;/p&gt;

&lt;p&gt;As the title implies, this post is going to focus specifically on how to improve the &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/introduction"&gt;Next.js config&lt;/a&gt; by focusing on a handful of the top/best config options used in other build tools like &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔧 More Options
&lt;/h3&gt;

&lt;p&gt;In general I feel like there should be more options added to the config file. Looking at the &lt;a href="https://vitejs.dev/config/#root"&gt;shared options in Vite&lt;/a&gt;, here are just a few that would make a lot of sense to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://vitejs.dev/config/#build-target"&gt;&lt;code&gt;build&lt;/code&gt;&lt;/a&gt;: Customize the build output&lt;/p&gt;

&lt;p&gt;Offer more customization for the build configuration of your project. In the case of Next.js I feel a lot of existing build options could be moved here. EG: &lt;code&gt;swcMinify&lt;/code&gt;, &lt;code&gt;webpack&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://vitejs.dev/config/#define"&gt;&lt;code&gt;define&lt;/code&gt;&lt;/a&gt;: Define global constant replacements&lt;/p&gt;

&lt;p&gt;As a good alternative to having to add &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; environment variables all the time you can use this to define public constant variables.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vitejs.dev/config/#logLevel"&gt;&lt;code&gt;logLevel&lt;/code&gt;&lt;/a&gt;: Adjust console output verbosity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://vitejs.dev/config/#plugins"&gt;&lt;code&gt;plugins&lt;/code&gt;&lt;/a&gt;: Array of plugins to use&lt;/p&gt;

&lt;p&gt;Plugins are a big feature that I'm going to dive into more later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://vitejs.dev/config/#root"&gt;&lt;code&gt;root&lt;/code&gt;&lt;/a&gt;: Project root directory&lt;/p&gt;

&lt;p&gt;Re-learning Next.js over the past few months I have been researching a number of open source projects, many of which like to point to a custom root directory. Most commonly a &lt;code&gt;src/&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://vitejs.dev/config/#server-host"&gt;&lt;code&gt;server&lt;/code&gt;&lt;/a&gt;: Server options&lt;/p&gt;

&lt;p&gt;Customize the server configuration with options like &lt;code&gt;cors&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The majority of these options simply add project structure flexability &amp;amp; extensibility for more advanced users.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❗ TypeScript
&lt;/h3&gt;

&lt;p&gt;Moving onto what is, to me at least, one of the most obvious features. TypeScript support.&lt;/p&gt;

&lt;p&gt;I personally don't like using the &lt;a href="https://flaviocopes.com/commonjs/"&gt;CommonJS syntax&lt;/a&gt; where possible. To the point that for most projects now when I want to write a simple Node script I quickly add &lt;a href="https://tsup.egoist.sh/"&gt;&lt;code&gt;tsup&lt;/code&gt;&lt;/a&gt; so I can use TypeScript &amp;amp; a number of other handy features.&lt;br&gt;
One scenario I particularly hate is when a library offers a object type definition &amp;amp; but you have to use the JSDoc &lt;code&gt;@type {import('...').TypeName}&lt;/code&gt; syntax to use it.&lt;/p&gt;

&lt;p&gt;For comparison: This, to me, is ugly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @type {import('next').NextConfig}
 */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This however, looks much cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No, type safety isn't needed for a config file. But when writing it it can be very helpful to have tools, such as intellisense, to help prompt any issues with your setup or help save you from having to even look up the conig documentation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;next.config.ts&lt;/code&gt; has been talked about for 3 years now &amp;amp; has still not been added. &lt;a href="https://github.com/vercel/next.js/issues/5318"&gt;This issue&lt;/a&gt; has been tracking this request since first posted in 2018 and still to this day has people asking for it.&lt;/p&gt;

&lt;p&gt;I can very much understand if there are more pressing features, bugs, etc that need to be addressed. But &lt;a href="https://nextjs.org/blog/next-12"&gt;Next.js 12 has just been released&lt;/a&gt;, the Next.js core has been running on TypeScript since 2019 &amp;amp; Vercel has constantly preached how amazing TypeScript is to the point it works out of the box pretty much.&lt;br&gt;
And yet, the Next.js config still only allows a CommonJS JavaScript file.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔌 Plugins
&lt;/h3&gt;

&lt;p&gt;One of the second features that I think would have the biggest &amp;amp; best impact on Next.js would be an all new plugins system.&lt;/p&gt;

&lt;p&gt;Currently Next.js does provide a plugin system of sorts but is, as far as I can tell, community made &amp;amp; managed by nesting functions that return an updated Next.js configuration object. Not so pretty.&lt;/p&gt;

&lt;p&gt;In an indeal world an all new plugins system could make it even easier to bootstap &amp;amp; customize a Next.js project with your favourite tools in a matter of minutes, not hours like it does today.&lt;/p&gt;

&lt;p&gt;Imagine if you could add &lt;a href="https://tailwindcss.com/"&gt;TailwindCSS&lt;/a&gt; to your Next.js project by just doing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; next-plugin-tailwindcss
&lt;span class="c"&gt;# Or with Yarn&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; next-plugin-tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @type {import('next').NextConfig}
 */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-plugin-tailwindcss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's basically how Vite's plugin system works &amp;amp; how nice &amp;amp; easy it is to work with. It has a &lt;a href="https://vitejs.dev/config/#plugins"&gt;&lt;code&gt;plugins&lt;/code&gt;&lt;/a&gt; property that allows you to simply provide an array of plugin methods that you call with your desired options.&lt;/p&gt;

&lt;p&gt;This system has become so popular that there are a huge number of plugins to let you to almost anything and everything you could need. Here is a few examples of my favourites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.npmjs.com/package/vite-plugin-icons"&gt;&lt;code&gt;vite-plugin-icons&lt;/code&gt;&lt;/a&gt;: Access thousands of icons via &lt;a href="https://iconify.design/"&gt;Iconify&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.npmjs.com/package/vite-plugin-markdown"&gt;&lt;code&gt;vite-plugin-markdown&lt;/code&gt;&lt;/a&gt;: Import markdown files content, frontmatter, etc.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.npmjs.com/package/vite-plugin-pwa"&gt;&lt;code&gt;vite-plugin-pwa&lt;/code&gt;&lt;/a&gt;: Generate all assets needed to make your app PWA ready.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://windicss.org/integrations/vite.html"&gt;&lt;code&gt;vite-plugin-windicss&lt;/code&gt;&lt;/a&gt;: Quickly add WindiCSS (TailwindCSS + some cool extras).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  😎 Merged
&lt;/h3&gt;

&lt;p&gt;With all of those features all put together you end up with something that looks a little like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TailwindCSS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-plugin-windicss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;swc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isServer&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;TailwindCSS&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  😇 Simplicity
&lt;/h3&gt;

&lt;p&gt;One argunment I could see for NOT to make all these big, possibly breaking, changes to the config system would be "But Next.js is so simple to use. This will just add complexity".&lt;/p&gt;

&lt;p&gt;Fair point. But if done correctly it could be backwards compatible with the current config system &amp;amp; then allows more experienced developers who do want to utilize some of the new features, such as plugins, TypeScript support, etc, can do so.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 Conclusion
&lt;/h3&gt;

&lt;p&gt;Of course all of this is just my opinion on how to improve the Next.js config system &amp;amp; how I think it should look, but others may have differing opinions / preferences, which I am very open to discussing.&lt;/p&gt;

&lt;p&gt;For now though I feel like this needs to be something that should be discussed more in the community &amp;amp; looked into further as it could very much make Next.js even more powerful than it already is.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Hi, I'm Meadowcottage 👋🏻</title>
      <dc:creator>nuro</dc:creator>
      <pubDate>Wed, 19 Jul 2017 02:53:26 +0000</pubDate>
      <link>https://forem.com/meadowcottage/hi-im-meadowcottage-</link>
      <guid>https://forem.com/meadowcottage/hi-im-meadowcottage-</guid>
      <description>

</description>
      <category>introductions</category>
    </item>
  </channel>
</rss>
