<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Nico Horn's blog</title>
        <link>https://nicohorn.com</link>
        <description>Welcome to my blog! Here I post my own thoughts and views about tech, philosophy and pretty much anything that comes to my mind.</description>
        <lastBuildDate>Mon, 28 Oct 2024 01:43:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for Node.js</generator>
        <image>
            <title>Nico Horn's blog</title>
            <url>https://nicohorn.com/ms-icon-144x144.png</url>
            <link>https://nicohorn.com</link>
        </image>
        <copyright>All rights reserved 2024, Nico Horn</copyright>
        <item>
            <title><![CDATA[Create an RSS feed from a Next.js website]]></title>
            <link>https://nicohorn.com/en-US/blog/b05b746b-2120-4a4f-8b38-4d06d3378dbc</link>
            <guid>https://nicohorn.com/en-US/blog/b05b746b-2120-4a4f-8b38-4d06d3378dbc</guid>
            <pubDate>Fri, 15 Mar 2024 06:59:10 GMT</pubDate>
            <description><![CDATA[It's been a while since I wanted to have my own RSS feed, so I got to work on it. Here I'm sharing how to do it using Next.js App router. ]]></description>
            <content:encoded><![CDATA[<p>RSS feeds are to me the original way to surf the web. Lately I've been feeling that the mainstream internet (social media such as Instagram, Tiktok, X , etc) is losing its spark. Some people adhere to the "dead internet theory", which might be part of the issue. What I do think is that back in the day, people used to connect and share more genuine stuff on the internet, and one of the most efficient ways is using an RSS feed. Here's how to do it using <code>Next.js</code></p><p>Before we get to it, don't forget to subscribe to mine! 😁</p><p></p><h1><strong>What we'll do:</strong></h1><ol><li><p>Get blog entries data.</p></li><li><p>Create a util function that writes XML and JSON files to our server file system (effectively creating the RSS feed).</p></li><li><p>Call the function at build time and making it run with a Vercel cron job.</p></li></ol><p></p><p></p><p>First of all, install the following dependency into your project</p><pre><code class="language-node">npm install feed</code></pre><h1><strong>Create a util function</strong></h1><p>First, I created a JavaScript file. This is where the whole logic to create the RSS feed resides.</p><pre><code class="language-javascript">/src/utils/generateRSSfeed.js

import { Feed } from "feed"

const generateRssFeed = async () =&gt; {
    //Code to generate the RSS feed
}

export default generateRssFeed();</code></pre><h1><strong>Get blog entries data</strong></h1><p>Using the JavaScript <code>fetch</code> API:</p><pre><code class="language-javascript">/src/utils/generateRSSfeed.js

    const postsFetch = await fetch(`https://nicohorn.com/api/blog_entry`, {
        method: "GET",
    });</code></pre><p>Let's define some useful variables</p><pre><code class="language-typescript">/src/utils/generateRSSfeed.js    

    const postsFetch = await fetch(`https://nicohorn.com/api/blog_entry`, {
        method: "GET",
    });
    const posts = await postsFetch.json();
    const siteURL = "https://nicohorn.com";
    const date = new Date();
    const author = {
        name: "Nico Horn",
        email: "contact@nicohorn.com",
        link: "https://nicohorn.com"
    }</code></pre><p>Now, using the Feed library, we do the following:</p><pre><code class="language-typescript">/src/utils/generateRSSfeed.js

    const feed = new Feed({
        title: "Nico Horn's blog",
        description: "Welcome to my blog! Here I post my own thoughts and views about tech, 
        philosophy and pretty much anything that comes to my mind.",
        id: siteURL,
        link: siteURL,
        image: `https://nicohorn.com/ms-icon-144x144.png`,
        copyright: `All rights reserved ${date.getFullYear()}, Nico Horn`,
        updated: date,
        generator: "Feed for Node.js",
        feedLinks: {
            rss2: `${siteURL}/rss/feed.xml`,
            json: `${siteURL}/rss/feed.json`,
            atom: `${siteURL}/rss/atom.xml`
        },
        author
    })</code></pre><p>Add each entry to the feed we just created:</p><pre><code class="language-typescript">/src/utils/generateRSSfeed.js

    posts?.forEach((post) =&gt; {
        const entryURL = `${siteURL}/en-US/blog/${post.id}`

        feed.addItem({
            title: post.title,
            id: entryURL,
            link: entryURL,
            description: post.description,
            content: post.content,
            image: post.cover_image,
            author: [author],
            contributor: [author],
            date: new Date(post.created_at),
        })
    })</code></pre><p>Lastly, use the <code>fs</code> Node module to create a folder and write the files in the server:</p><pre><code class="language-typescript">/src/utils/generateRSSfeed.js
    
    fs.mkdirSync("./public/rss", { recursive: true });
    fs.writeFileSync("./public/rss/feed.xml", feed.rss2());
    fs.writeFileSync("./public/rss/atom.xml", feed.atom1());
    fs.writeFileSync("./public/rss/feed.json", feed.json1());</code></pre><p><strong>Final code:</strong></p><pre><code class="language-typescript">/src/utils/generateRSSfeed.js

import { Feed } from "feed"
import fs from "fs";

const generateRssFeed = async () =&gt; {

    //This console log will appear in the console at build time.
    console.log("Creating RSS feed");
    const postsFetch = await fetch(`https://nicohorn.com/api/blog_entry`, {
        method: "GET",
    });
    const posts = await postsFetch.json();
    const siteURL = "https://nicohorn.com";
    const date = new Date();
    const author = {
        name: "Nico Horn",
        email: "contact@nicohorn.com",
        link: "https://nicohorn.com"
    }

    const feed = new Feed({
        title: "Nico Horn's blog",
        description: "Welcome to my blog! Here I post my own thoughts and views about tech, philosophy and pretty much anything that comes to my mind.",
        id: siteURL,
        link: siteURL,
        image: `https://nicohorn.com/ms-icon-144x144.png`,
        copyright: `All rights reserved ${date.getFullYear()}, Nico Horn`,
        updated: date,
        generator: "Feed for Node.js",
        feedLinks: {
            rss2: `${siteURL}/rss/feed.xml`,
            json: `${siteURL}/rss/feed.json`,
            atom: `${siteURL}/rss/atom.xml`
        },
        author
    })

    posts?.forEach((post) =&gt; {
        const entryURL = `${siteURL}/en-US/blog/${post.id}`

        feed.addItem({
            title: post.title,
            id: entryURL,
            link: entryURL,
            description: post.description,
            content: post.content,
            image: post.cover_image,
            author: [author],
            contributor: [author],
            date: new Date(post.created_at),
        })
    })

    fs.mkdirSync("./public/rss", { recursive: true });
    fs.writeFileSync("./public/rss/feed.xml", feed.rss2());
    fs.writeFileSync("./public/rss/atom.xml", feed.atom1());
    fs.writeFileSync("./public/rss/feed.json", feed.json1());

}

//This one I use it on the endpoint that the cron job runs.
export { generateRssFeed }
//It's exported like this, executed, because it'll be caled directly by a Node command in the CLI.
export default generateRssFeed();</code></pre><h1><strong>Call the function at build time</strong></h1><p>Simply add the following command in the <code>package.json</code></p><pre><code class="language-json">node ./src/utils/generateRSSfeed.js</code></pre><pre><code class="language-json">/package.json 

  "scripts": {
    "dev": "next dev",
    "build": "node ./src/utils/generateRSSfeed.js &amp;&amp; next build",
    "start": "next start",
    "lint": "next lint"
  },</code></pre><p>Also, don't forget to add <code>"type": "module"</code> at the root level in the <code>package.json</code>.</p><p>That should be it. I ran into a few problems such as having to rename the <code>postcss.config.js</code> to <code>postcss.config.cjs</code> given that it's a CommonJS module.</p><h1><strong>Cron job</strong></h1><p>Now, what I did so far only works at build time, which is not all that useful if we want to keep our feed updated automatically. To solve this, I'm using a cron job that Vercel let us configure directly into the project. We could also call the function that creates the RSS feed in the POST endpoint that I use to create the blog post, but I had never used cron jobs in Vercel so I thought it would be a cool opportunity to try it out.</p><p>Learn more about cron jobs in Vercel: <a target="_blank" rel="noopener noreferrer nofollow" href="https://vercel.com/docs/cron-jobs">Cron Jobs (</a><a target="_blank" rel="noopener noreferrer nofollow" href="http://vercel.com">vercel.com</a><a target="_blank" rel="noopener noreferrer nofollow" href="https://vercel.com/docs/cron-jobs">)</a></p><p>Create a route.ts into the api folder in the project. The structure should look like this:</p><blockquote><p>-/api</p><p>-/cron</p><p>-route.ts</p></blockquote><pre><code class="language-typescript">/api/cron/route.ts

import { generateRssFeed } from '@/utils/generateEnglishRSSfeed';
import type { NextRequest } from 'next/server';

export function GET(request: NextRequest) {
    const authHeader = request.headers.get('authorization');
    if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
        return new Response('Unauthorized', {
            status: 401,
        });
    }

    generateRssFeed();

    return Response.json({ success: true });
}</code></pre><p>And now, create a <code>vercel.json</code> file at the root level of your project to configure the cron job. In this case, this cron job runs everyday at 1:00AM.</p><p>To learn more about cron jobs: <a target="_blank" rel="noopener noreferrer nofollow" href="https://cronitor.io/guides/cron-jobs?utm_source=crontabguru&amp;utm_campaign=cron_reference&amp;utm_content=22">Cron Jobs: The Complete Guide for 2024 (</a><a target="_blank" rel="noopener noreferrer nofollow" href="http://cronitor.io">cronitor.io</a><a target="_blank" rel="noopener noreferrer nofollow" href="https://cronitor.io/guides/cron-jobs?utm_source=crontabguru&amp;utm_campaign=cron_reference&amp;utm_content=22">)</a></p><pre><code class="language-json">{
  "crons": [
    {
      "path": "/api/cron",
      "schedule": "0 1 * * *"
    }
  ]
}</code></pre><p>That's all, enjoy!</p>]]></content:encoded>
            <author>contact@nicohorn.com (Nico Horn)</author>
            <enclosure url="https://wfqmvtjbaiggzoiwhxjy.supabase.co/storage/v1/object/public/nicohorn_website/blog_images/OIG4.CY6.cbeM9..jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Detect click outside HTML element]]></title>
            <link>https://nicohorn.com/en-US/blog/154107f0-82c0-4a73-bfd4-b216df229821</link>
            <guid>https://nicohorn.com/en-US/blog/154107f0-82c0-4a73-bfd4-b216df229821</guid>
            <pubDate>Sun, 03 Mar 2024 09:12:18 GMT</pubDate>
            <description><![CDATA[This is a fairly easy thing to do, but I never found a concise and simple explanation for it. This is my attempt at doing that.]]></description>
            <content:encoded><![CDATA[<h2>Problem:<strong> detect a click outside an HTML Element</strong></h2><p>Although there's many component libraries that solve this problem, it's always nice when building our own website to do all the little things by ourselves. In my case, I've always liked doing modals from scratch. Well, not so from scratch since I'm using React to manage the state, but otherwise, this approach works for plain HTML and JS. In my case, for a better user experiences, I like making my modals so that they close when the user click "outside" it, as shown in the image below.</p><p></p><img class="tiptap_image" src="https://wfqmvtjbaiggzoiwhxjy.supabase.co/storage/v1/object/public/nicohorn_website/blog_images/Untitled-2024-03-02-0056.png" alt="blog image"><p></p><p>So my approach was the following: when we attach an event listener to an element, such as the <code>#modal</code> we get an output with an object that contains all the info related to the event. In this case, the event listener would be an <code>onclick</code> so I went with the following solution, which is by far the simplest solution I know so far:</p><p></p><pre><code>function closeModal(event){
   if(event.target !== document.getElementById("background_modal")){
      return //Do nothing, click is inside the modal.
   }
   //Code that closes the modal.
   //The program only reaches this part if the previous condition isn't true.
}</code></pre><pre><code>#This is the HTML structure
&lt;div id="background_modal"&gt;
   &lt;div onclick="closeModal"&gt;
     #Modal content
   &lt;/div&gt;
&lt;/div&gt;</code></pre><p>That's it, try it out. Have fun!</p>]]></content:encoded>
            <author>contact@nicohorn.com (Nico Horn)</author>
            <enclosure url="https://wfqmvtjbaiggzoiwhxjy.supabase.co/storage/v1/object/public/nicohorn_website/blog_images/valery-sysoev-p9OkL4yW3C8-unsplash.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Hey there]]></title>
            <link>https://nicohorn.com/en-US/blog/e6d998e8-a1e1-4288-beb8-597e0a39e70d</link>
            <guid>https://nicohorn.com/en-US/blog/e6d998e8-a1e1-4288-beb8-597e0a39e70d</guid>
            <pubDate>Fri, 01 Mar 2024 05:08:58 GMT</pubDate>
            <description><![CDATA[Welcome to my blog! This is the first entry (in english) I'm making. Let me introduce myself.]]></description>
            <content:encoded><![CDATA[<h1>Hello World</h1><p>Its been a long time since I wanted to craft and publish my own blog, been postponing it for more than 3 years actually. There are a lot of options out there for people who love to write but don't want to spend some serious hours coding their own blog, which I understand and respect, but in my case, I always wanted to do it my way, having to learn and try things out, the old fashioned way. The thing is, I love building stuff and I love sharing my thoughts and experiences, so that's a pretty good excuse for building this from scratch.</p><img class="tiptap_image" src="https://wfqmvtjbaiggzoiwhxjy.supabase.co/storage/v1/object/public/nicohorn_website/blog_images/nicolas.png" alt="blog image"><p>I'm Nico, I'm a software engineer, computer science graduate and I also teach at a local university. I have gathered some knowledge in the past few years as a developer but I can't stay put, I always feel that there's so much more to learn and, as the cliché goes, the more I learn, the less I know. This is my space to share that journey, to share what I learn and my thoughts on it. Knowledge should be shared and I'm here for it. </p>]]></content:encoded>
            <author>contact@nicohorn.com (Nico Horn)</author>
            <enclosure url="https://wfqmvtjbaiggzoiwhxjy.supabase.co/storage/v1/object/public/nicohorn_website/blog_images/sajad-nori-21mJd5NUGZU-unsplash.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>