<?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: Ramkarthik</title>
    <description>The latest articles on Forem by Ramkarthik (@ramkarthik).</description>
    <link>https://forem.com/ramkarthik</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%2F481220%2Fde2e292f-37f8-4efb-92c1-375ebcf86738.jpg</url>
      <title>Forem: Ramkarthik</title>
      <link>https://forem.com/ramkarthik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ramkarthik"/>
    <language>en</language>
    <item>
      <title>Create an Astro blog from scratch</title>
      <dc:creator>Ramkarthik</dc:creator>
      <pubDate>Wed, 05 Jun 2024 05:24:12 +0000</pubDate>
      <link>https://forem.com/ramkarthik/create-an-astro-blog-from-scratch-5870</link>
      <guid>https://forem.com/ramkarthik/create-an-astro-blog-from-scratch-5870</guid>
      <description>&lt;p&gt;Astro is a web framework for creating content-driven websites. It allows you to build extremely fast websites. It is perfect for building blogs and we are going to do exactly that. We are going to build an Astro blog from scratch.&lt;/p&gt;

&lt;p&gt;You can find the complete code here: &lt;a href="https://github.com/Ramkarthik/astro-blog-tutorial"&gt;https://github.com/Ramkarthik/astro-blog-tutorial&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the blog we are building from scratch: &lt;a href="https://quick-astro-blog-tutorial.vercel.app/"&gt;https://quick-astro-blog-tutorial.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will be going over the steps below to build this blog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an Astro project&lt;/li&gt;
&lt;li&gt;Add some basic styling&lt;/li&gt;
&lt;li&gt;Create a layout&lt;/li&gt;
&lt;li&gt;Set up default website configurations&lt;/li&gt;
&lt;li&gt;Create an About page&lt;/li&gt;
&lt;li&gt;Create a Nav header&lt;/li&gt;
&lt;li&gt;Create a folder to add the blog content (the easy route)&lt;/li&gt;
&lt;li&gt;Use Content Collections for our blog&lt;/li&gt;
&lt;li&gt;Setting up the dynamic routes for our blog&lt;/li&gt;
&lt;li&gt;Getting Markdown content from collection entry&lt;/li&gt;
&lt;li&gt;Create a blog listing page&lt;/li&gt;
&lt;li&gt;Using our first Astro Integration to add SEO&lt;/li&gt;
&lt;li&gt;Create an RSS feed for the blog&lt;/li&gt;
&lt;li&gt;Add Sitemap and robots.txt file&lt;/li&gt;
&lt;li&gt;Preparing for deployment&lt;/li&gt;
&lt;li&gt;Next steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we start, you want to make sure you have Node.js installed. Astro recommends using &lt;code&gt;v18.17.1&lt;/code&gt; or &lt;code&gt;v20.3.0&lt;/code&gt; or higher. ( &lt;code&gt;v19&lt;/code&gt; is not supported.)&lt;/p&gt;

&lt;h3&gt;
  
  
  0. Create an Astro project
&lt;/h3&gt;

&lt;p&gt;Open Terminal and navigate to the folder where you want to create the blog and run this command to create an Astro 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 create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you a couple of questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1. Where should we create your new project? ./name-of-your-project
2. How would you like to start your new project? Empty &lt;span class="o"&gt;(&lt;/span&gt;You can use the blog template here but this is to learn how to &lt;span class="nb"&gt;set &lt;/span&gt;up one from scratch, so we will choose the Empty template&lt;span class="o"&gt;)&lt;/span&gt;
3. Do you plan to use TypeScript? Yes &lt;span class="o"&gt;(&lt;/span&gt;You can choose no &lt;span class="k"&gt;if &lt;/span&gt;you don&lt;span class="s1"&gt;'t want to use TypeScript)
4. How strict should TypeScript be? Strict
5. Install dependencies? Yes
6. Initialize a new git repository? Yes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the Astro project. Navigate into the folder to run the project. Open the project in your favorite editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;name-of-your-project
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;a href="https://localhost:4321"&gt;https://localhost:4321&lt;/a&gt; and you will see the page displaying &lt;strong&gt;Astro&lt;/strong&gt;. If you chose the blog template in step 2, you may see a different page.&lt;/p&gt;

&lt;p&gt;Before we get started, let's modify the &lt;code&gt;tsconfig.json&lt;/code&gt; file a little bit to make things easier for referencing different components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./src/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"astro/tsconfigs/strict"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add some basic styling
&lt;/h3&gt;

&lt;p&gt;We can write the CSS from scratch as well or use something like Tailwind. To keep things easier, we will use one of the many classless CSS. A classless CSS styles a page based on the HTML elements instead of using class names. We are using this to make it easier for us to style the HTML rendered using Markdown. The Astro Markdown render outputs a basic HTML and using a classless CSS, we don't have to worry too much about styling each element. Classless CSS are also lightweight so our blog will be extremely fast.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://github.com/dbohdan/classless-css"&gt;many classless CSS options&lt;/a&gt;. For this project (and my blog), I'm going to be using &lt;a href="https://oxal.org/projects/sakura/"&gt;Sakura&lt;/a&gt;. You can use any of those options.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the CSS file&lt;/li&gt;
&lt;li&gt;Create a folder named &lt;code&gt;css&lt;/code&gt; inside the &lt;code&gt;public&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Paste the file and rename it to &lt;code&gt;style.css&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now go to &lt;code&gt;\src\index.astro&lt;/code&gt; and add the CSS file to the end of the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag.&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="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/css/style.css"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the changes already. Without adding any classes, we can see that our page is already styled.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a layout
&lt;/h3&gt;

&lt;p&gt;Layouts are basic templates that can be shared across different pages. We want the basic &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, and &lt;code&gt;footer&lt;/code&gt; elements to be available on each page. So let's create a base layout that will store these as a template.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder named &lt;code&gt;layouts&lt;/code&gt; under the &lt;code&gt;src&lt;/code&gt; folder (&lt;code&gt;\src\layouts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a file inside the layouts folder named &lt;code&gt;Base.astro&lt;/code&gt; (&lt;code&gt;\src\layouts\Base.astro&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The page we are seeing now comes from the &lt;code&gt;pages\index.astro&lt;/code&gt; page. This is the default page or the entry point. We will be creating both the static pages (ex: &lt;code&gt;\about&lt;/code&gt;) and the dynamic pages (ex: &lt;code&gt;\blog\my-first-post&lt;/code&gt;) by creating Astro pages inside the &lt;code&gt;pages&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Now let's move all the code inside the &lt;code&gt;index.astro&lt;/code&gt; page to the &lt;code&gt;Base.astro&lt;/code&gt; page we created.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;index.astro&lt;/code&gt; and add the &lt;code&gt;Base.astro&lt;/code&gt; layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
---
&amp;lt;Base /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice a couple of things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Astro, you write JavaScript code within the &lt;code&gt;three dash separators&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Similar to React, you can also write JavaScript alongside HTML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you refresh the page now, you should not see any difference because we only moved the content from &lt;code&gt;index.astro&lt;/code&gt; to &lt;code&gt;Base.astro&lt;/code&gt; and referenced &lt;code&gt;Base.astro&lt;/code&gt; from &lt;code&gt;index.astro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This brings us to a new concept in Astro... &lt;strong&gt;Slots&lt;/strong&gt;. Astro uses &lt;code&gt;&amp;lt;slot/&amp;gt;&lt;/code&gt; to inject the child components.&lt;/p&gt;

&lt;p&gt;Let's go to &lt;code&gt;Base.astro&lt;/code&gt;. Not every line of code there belongs in a template, mainly the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag. Replace that with &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;index.astro&lt;/code&gt;, and add the &lt;code&gt;h1&lt;/code&gt; tag within the &lt;code&gt;&amp;lt;Base&amp;gt;&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
---
&amp;lt;Base&amp;gt;
    &amp;lt;h1&amp;gt;Astro&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the browser and you will not see any changes. What we did was move the template code from &lt;code&gt;index.astro&lt;/code&gt; into &lt;code&gt;Base.astro&lt;/code&gt; and use the layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set up default website configurations
&lt;/h3&gt;

&lt;p&gt;We need some basic information about our website that we need to display in many places. We don't want to type them everywhere. We want to store these configurations in one place and refer to them wherever we need them so that if we want to make any changes, we only have to change the configuration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder named &lt;code&gt;utils&lt;/code&gt; inside the &lt;code&gt;src&lt;/code&gt; folder (&lt;code&gt;src\utils&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a file named &lt;code&gt;AppConfig.ts&lt;/code&gt; inside the &lt;code&gt;utils&lt;/code&gt; folder (&lt;code&gt;AppConfig.js&lt;/code&gt; if you don't want TypeScript)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const AppConfig = {
    author: "Author Name",
    title: "My personal website",
    description: "This is my personal website",
    image: "/images/social.png", // this will be used as the default social preview image
    twitter: "@handle",
    site: "https://yourwebsite.com/" // this is your website URL
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our website currently displays &lt;code&gt;Astro&lt;/code&gt;. Let's change that to show our name from the config file we created. Let's also bring in the description and display that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";
---
&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{AppConfig.title}&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;{AppConfig.description}&amp;lt;/p&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Create an About page
&lt;/h3&gt;

&lt;p&gt;Now that we have the home page, let's see how we can create a new page - in this case, an About (&lt;code&gt;https://localhost:4321/about&lt;/code&gt;) page.&lt;/p&gt;

&lt;p&gt;Astro uses file-based routing. Let's see some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/pages/index.astro -&amp;gt; mysite.com/
src/pages/about.astro -&amp;gt; mysite.com/about
src/pages/about/index.astro  -&amp;gt; mysite.com/about
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there are two ways to create an About page. We will use the first approach and create a new file named &lt;code&gt;about.astro&lt;/code&gt; inside the &lt;code&gt;pages&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
---
&amp;lt;Base&amp;gt;
    &amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;http://localhost:4321/about&lt;/code&gt; and you should see the page we created.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create a Nav header
&lt;/h3&gt;

&lt;p&gt;We now have two pages, so we need a way to link to them from our home page as well as our other pages. We will do this by creating a nav header. Since we want this header to appear on all our pages, we will add it to the &lt;code&gt;Base.astro&lt;/code&gt; layout page.&lt;/p&gt;

&lt;p&gt;Instead of adding the code for the header directly to this file, we will create a separate component (&lt;code&gt;Nav.astro&lt;/code&gt;). From the React docs:&lt;br&gt;
"Components let you split the UI into independent, reusable pieces, and think about each piece in isolation."&lt;/p&gt;

&lt;p&gt;We will store all the components inside a separate folder called &lt;code&gt;components&lt;/code&gt; which we will create under the &lt;code&gt;src&lt;/code&gt; folder (&lt;code&gt;src\components&lt;/code&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder named &lt;code&gt;components&lt;/code&gt; inside the &lt;code&gt;src&lt;/code&gt; folder (&lt;code&gt;src\components&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a file named &lt;code&gt;Nav.astro&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's display our name on the left and the navigation links on the right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { AppConfig } from "@/utils/AppConfig";
---
&amp;lt;nav role="navigation"&amp;gt;
  &amp;lt;a href="/"&amp;gt;{AppConfig.author}&amp;lt;/a&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;a href="/about"&amp;gt;About&amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will style this in a minute. We only have two pages, so this approach is fine. But we will likely create more nav links like &lt;code&gt;blog&lt;/code&gt;, &lt;code&gt;rss&lt;/code&gt;, etc and there's a better way to manage that than adding a new line of code here with the name and the link.&lt;/p&gt;

&lt;p&gt;Let's go back to our AppConfig.ts and add our list of pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const AppConfig = {
        author: "Author Name",
        title: "My personal website",
        description: "This is my personal website",
        image: "/images/social.png", // this will be used as the default social preview image
        twitter: "@handle",
        site: "https://yourwebsite.com/",// this is your website URL
        pages: [{
            name: "About",
            link: "/about"
        }]
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now modify the &lt;code&gt;Nav.astro&lt;/code&gt; component to get the links from the &lt;code&gt;pages&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { AppConfig } from "@/utils/AppConfig";
---

&amp;lt;nav role="navigation" class="justify-between"&amp;gt;
  &amp;lt;a href="/"&amp;gt;{AppConfig.author}&amp;lt;/a&amp;gt;
  &amp;lt;div&amp;gt;
    {
      AppConfig.pages.map((p, index) =&amp;gt; {
        return (
          &amp;lt;span&amp;gt;
            &amp;lt;a href={p.link}&amp;gt;
              &amp;lt;small&amp;gt;{p.name}&amp;lt;/small&amp;gt;
            &amp;lt;/a&amp;gt;
            {index != AppConfig.pages.length - 1 &amp;amp;&amp;amp; &amp;lt;small&amp;gt;|&amp;lt;/small&amp;gt;}
          &amp;lt;/span&amp;gt;
        );
      })
    }
  &amp;lt;/div&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might be familiar with this syntax of mapping an array and returning JSX, if you've used React before. One thing you may notice is the missing &lt;code&gt;key&lt;/code&gt; property. Astro doesn't require a &lt;code&gt;key&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Also, notice the &lt;code&gt;class="justify-between"&lt;/code&gt; added to the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element. We will use this later to style the nav.&lt;/p&gt;

&lt;p&gt;You won't see any changes on the website yet because we haven't added the &lt;code&gt;Nav.astro&lt;/code&gt; component to our &lt;code&gt;Base.astro&lt;/code&gt; layout. Let's do that now. We will add the &lt;code&gt;&amp;lt;Nav /&amp;gt;&lt;/code&gt; component just above the slot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Nav from "@/components/Nav.astro";
---

&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width" /&amp;gt;
    &amp;lt;meta name="generator" content={Astro.generator} /&amp;gt;
    &amp;lt;title&amp;gt;Astro&amp;lt;/title&amp;gt;
    &amp;lt;link rel="stylesheet" href="/css/style.css" type="text/css" /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;Nav /&amp;gt;
    &amp;lt;slot /&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we go to &lt;code&gt;http://localhost:4321/&lt;/code&gt;, we should now see the title and the navigation links. Let's style this so that the title is on the left and the nav links are on the right. Remember the &lt;code&gt;nav="class"&lt;/code&gt; that we added to the &lt;code&gt;nav&lt;/code&gt; element before. We will use that to style the nav.&lt;/p&gt;

&lt;p&gt;Add this to the end of the &lt;code&gt;style.css&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;.justify-between {
  display: flex;
  justify-content: space-between;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect, we have the header set up now. In case you don't see the changes, press &lt;code&gt;Ctrl+R&lt;/code&gt; on the webpage to hard reload (disabling the cache). Let's move on to the blog (the interesting part).&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Create a folder to add the blog content (the easy route)
&lt;/h3&gt;

&lt;p&gt;We want to create a folder to store the contents of our blog. Each post will be a Markdown file. We will do the easy implementation first and then change that to match our needs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder named &lt;code&gt;blog&lt;/code&gt; inside the &lt;code&gt;pages&lt;/code&gt; folder (&lt;code&gt;pages\blog&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a sample Markdown file inside the &lt;code&gt;blog&lt;/code&gt; folder&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm going to create a file named &lt;code&gt;my-first-post.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: My first post
createdDate: "2024-05-18"
modifiedDate: "2024-05-18"
tags: ["first-tag"]
summary: "A summary of the post"
---

This is my first blog post written in Markdown.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content within the &lt;code&gt;three dashed separator&lt;/code&gt; &lt;code&gt;---&lt;/code&gt; is called &lt;strong&gt;frontmatter&lt;/strong&gt;. We will later use the information from the frontmatter to display on our page.&lt;/p&gt;

&lt;p&gt;Now go to the URL: &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt; and you should see a very basic version of your blog post content. You will not see the title yet and that's where frontmatter comes into play.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Use Content Collections for our blog
&lt;/h3&gt;

&lt;p&gt;Currently, we have the blog content inside the &lt;code&gt;pages&lt;/code&gt; folder. We want to keep the &lt;code&gt;pages&lt;/code&gt; folder for code and move our blog content to a separate folder so that we have a separation of concerns and it is also easier to manage it this way.&lt;/p&gt;

&lt;p&gt;Astro provides an API called &lt;code&gt;Content Collections&lt;/code&gt; starting from &lt;code&gt;astro@2.0.0&lt;/code&gt;. To use this feature, we have to create a folder named &lt;code&gt;content&lt;/code&gt; inside the &lt;code&gt;src&lt;/code&gt; folder. This &lt;code&gt;content&lt;/code&gt; folder is restricted for content collections and should not be used for anything else.&lt;/p&gt;

&lt;p&gt;So let's go ahead and move our blog folder from &lt;code&gt;src\pages&lt;/code&gt; to &lt;code&gt;src\content&lt;/code&gt;. If you followed the steps so far, your project folder should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.astro
node_modules
public
    css
        style.css
    favico.svg
src
    components
        Nav.astro
    content
        blog
            my-first-post.md
    layouts
        Base.astro
    pages
        about.astro
        index.astro
    utils
        AppConfig.ts
    env.t.ds
.gitignore
astro.config.mjs
package-lock.json
package.json
README.md
tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the URL &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt; will not work because we have moved the &lt;code&gt;blog&lt;/code&gt; folder within the &lt;code&gt;content&lt;/code&gt; folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Setting up the dynamic routes for our blog
&lt;/h3&gt;

&lt;p&gt;We want the URL &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt; to work again. We can see that we have the &lt;code&gt;\blog&lt;/code&gt; route which means we have to create a folder named &lt;code&gt;blog&lt;/code&gt; inside the &lt;code&gt;pages&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Once we create the folder, we need to set up a way to handle the dynamic part of the URL, called the &lt;code&gt;slug&lt;/code&gt;. In our case, the slug is &lt;code&gt;my-first-post&lt;/code&gt;. But for each post, this will change.&lt;/p&gt;

&lt;p&gt;Let's create a file named &lt;code&gt;[slug].astro&lt;/code&gt; inside the &lt;code&gt;blog&lt;/code&gt; folder (&lt;code&gt;src\content\blog\[slug].astro&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For the dynamic paths to work, we need to let Astro know of the different possible paths. We do this by implementing the &lt;code&gt;getStaticPaths&lt;/code&gt; function. For us to know the different paths available, we have to get each file inside the &lt;code&gt;content&lt;/code&gt; folder and return the &lt;code&gt;slug&lt;/code&gt;. We do this using the &lt;code&gt;getCollection&lt;/code&gt; API provided by Astro via the &lt;code&gt;astro:content&lt;/code&gt; module that is built-in.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getCollection&lt;/code&gt; API works only with folders inside the &lt;code&gt;content&lt;/code&gt; folder. We get the posts inside the &lt;code&gt;blog&lt;/code&gt; folder, map through each item, and return the &lt;code&gt;slug&lt;/code&gt; as a parameter and also the contents of each item as props. Astro has a nifty way to retrieve the props through &lt;code&gt;Astro.props&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While we are on this file, let's add some basic HTML as well. We will use the &lt;code&gt;Base.astro&lt;/code&gt; layout we created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;Title&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt; and it should now work. We have successfully migrated to the &lt;code&gt;Content Collections&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;But wait, where's the content of the post we saw earlier? We have to bring those in from &lt;code&gt;Astro.props&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Remember the frontmatter we added to our post? Let's first define a schema so that we get type safety. We need it when we get the values from &lt;code&gt;Astro.props&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;config.ts&lt;/code&gt; under the &lt;code&gt;content&lt;/code&gt; folder (don't add it inside the &lt;code&gt;blog&lt;/code&gt; folder).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { z, defineCollection } from 'astro:content';

const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    summary: z.string(),
    tags: z.array(z.string()),
    createdDate: z.string(),
    modifiedDate: z.string(),
  }),
});

export const collections = {
  'blog': blogCollection,
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we define the schema, we have to let Astro know to generate the types. We can do that either by stopping the server (&lt;code&gt;Ctrl+C&lt;/code&gt;) or by running &lt;code&gt;npm run astro sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's edit the &lt;code&gt;[slug].astro&lt;/code&gt; file to display the blog post title. For this, we have to extract the title from Astro.props and add this to the existing &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}

const { post } = Astro.props;
const { title, summary, createdDate, tags } = post.data;
---
&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you go to &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt;, you should see the title from the frontmatter of the post appear.&lt;/p&gt;

&lt;p&gt;What about the content?&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Getting Markdown content from collection entry
&lt;/h3&gt;

&lt;p&gt;We are writing our posts in Markdown. We want Astro to generate HTML for the markdown and display that. We do this by first using the &lt;code&gt;render()&lt;/code&gt; function Astro provides and then adding that to the HTML block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}

const { post } = Astro.props;
const { title, summary, createdDate, tags } = post.data;
const { Content } = await post.render();
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
  &amp;lt;Content /&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We call the &lt;code&gt;render()&lt;/code&gt; function on the &lt;code&gt;post&lt;/code&gt; object, store it as &lt;code&gt;Content&lt;/code&gt;, and then add that to the HTML as &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You should now see the blog post content when you go to &lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt;. Let's add some random markdown to the &lt;code&gt;my-first-post.md&lt;/code&gt; file to see how the Markdown is displayed on the page (styled using the Sakura classless CSS we added). You can copy and paste random markdown using &lt;a href="https://jaspervdj.be/lorem-markdownum/"&gt;Lorem Markdownum&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also want to display the tags and the created date. Let's bring those in as well from the &lt;code&gt;props&lt;/code&gt; and add that to the HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}

const { post } = Astro.props;
const { title, summary, createdDate, tags } = post.data;
const { Content } = await post.render();
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
  &amp;lt;div class="justify-between"&amp;gt;
    &amp;lt;div&amp;gt;
      {
        tags.map((t) =&amp;gt; {
          return (
            &amp;lt;small&amp;gt;
              &amp;lt;i&amp;gt;#{t}&amp;lt;/i&amp;gt;
            &amp;lt;/small&amp;gt;
          );
        })
      }
    &amp;lt;/div&amp;gt;
    &amp;lt;small&amp;gt;{createdDate}&amp;lt;/small&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;hr /&amp;gt;
  &amp;lt;Content /&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add more posts to play around with. Go to the &lt;code&gt;blog&lt;/code&gt; folder and create more files. For this example, we will create &lt;code&gt;my-second-post.md&lt;/code&gt; and &lt;code&gt;my-third-post.md&lt;/code&gt; with the same content as &lt;code&gt;my-first-post.md&lt;/code&gt; and change only the &lt;code&gt;frontmatter&lt;/code&gt; details like &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;createdDate&lt;/code&gt;, and &lt;code&gt;tags&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With that, you should now be able to access the following URLs:&lt;br&gt;
&lt;code&gt;http://localhost:4321/blog/my-first-post&lt;/code&gt;&lt;br&gt;
&lt;code&gt;http://localhost:4321/blog/my-second-post&lt;/code&gt;&lt;br&gt;
&lt;code&gt;http://localhost:4321/blog/my-third-post&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Great! But we now need a blog listing page where readers can find all the blog posts as a list.&lt;/p&gt;
&lt;h3&gt;
  
  
  10. Create a blog listing page
&lt;/h3&gt;

&lt;p&gt;We want the listing page to be available at &lt;code&gt;/blog&lt;/code&gt; which means, you guessed it, we have to add either a &lt;code&gt;blog.astro&lt;/code&gt; find directly inside the &lt;code&gt;pages&lt;/code&gt; folder or add an &lt;code&gt;index.astro&lt;/code&gt; page inside the &lt;code&gt;pages\blog&lt;/code&gt; folder. We will do the latter. We will also bring in the &lt;code&gt;Base.astro&lt;/code&gt; layout (see how we are reusing the layout?).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
---

&amp;lt;Base&amp;gt;
    &amp;lt;h1&amp;gt;Posts&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you navigate to &lt;code&gt;http://localhost:4321/blog&lt;/code&gt;, you should see the blog listing page. Now we want to list the blog posts here.&lt;/p&gt;

&lt;p&gt;We will use the &lt;code&gt;getCollection()&lt;/code&gt; function to get the list of posts from the &lt;code&gt;blog&lt;/code&gt; folder and then map over each item to display them. We will also sort the posts based on the &lt;code&gt;createdDate&lt;/code&gt; we have defined in the frontmatter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";

const posts = await getCollection("blog");
const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;Posts&amp;lt;/h1&amp;gt;
  &amp;lt;ul&amp;gt;
    {
      sortedPosts.map((p) =&amp;gt; {
        return (
          &amp;lt;li&amp;gt;
            &amp;lt;a href={"/blog/" + p.slug}&amp;gt;{p.data.title}&amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
        );
      })
    }
  &amp;lt;/ul&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also do this for the home page and list the five most recent blog posts by editing the &lt;code&gt;src\pages\index.astro&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;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";

const posts = await getCollection("blog");

const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{AppConfig.title}&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;{AppConfig.description}&amp;lt;/p&amp;gt;
  &amp;lt;h3&amp;gt;Posts&amp;lt;/h3&amp;gt;
  &amp;lt;ul&amp;gt;
    {
      sortedPosts.slice(0, 5).map((p) =&amp;gt; {
        return (
          &amp;lt;li&amp;gt;
            &amp;lt;a href={"/blog/" + p.slug}&amp;gt;{p.data.title}&amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
        );
      })
    }
  &amp;lt;/ul&amp;gt;
  &amp;lt;a href="/blog"&amp;gt;Click here&amp;lt;/a&amp;gt; to view the archive.
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add some links for easy navigation.&lt;/p&gt;

&lt;p&gt;First, we will add a link for our blog listing page to the header. Since the links in the header come from the &lt;code&gt;pages&lt;/code&gt; property in &lt;code&gt;AppConfig.ts&lt;/code&gt; file, we will add a link to the blog listing page to the &lt;code&gt;pages&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const AppConfig = {
        author: "Author Name",
        title: "My personal website",
        description: "This is my personal website",
        image: "/images/social.png", // this will be used as the default social preview image
        twitter: "@handle",
        site: "https://yourwebsite.com/", // this is your website URL
        pages: [{
            name: "Blog",
            link: "/blog"
        },{
            name: "About",
            link: "/about"
        }]

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also add links to the previous post and the next post (if available) to the end of the blog post. To do this, we will retrieve the list of blog posts using the &lt;code&gt;getCollection()&lt;/code&gt; function, sort the posts, find the index of the current post in the sorted list, and then identify the previous and next posts to display in the HTML. We do this by editing the &lt;code&gt;[slug].astro&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;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}

const { post } = Astro.props;
const { title, summary, createdDate, tags } = post.data;
const { Content } = await post.render();
const posts = await getCollection("blog");

const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});

const index = sortedPosts.findIndex((c: any) =&amp;gt; {
  return c.slug == post.slug;
});

const prev = index == 0 ? undefined : sortedPosts[index - 1];
const next =
  index == sortedPosts.length - 1 ? undefined : sortedPosts[index + 1];
---

&amp;lt;Base&amp;gt;
  &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
  &amp;lt;div class="justify-between"&amp;gt;
    &amp;lt;div&amp;gt;
      {
        tags.map((t) =&amp;gt; {
          return (
            &amp;lt;small&amp;gt;
              &amp;lt;i&amp;gt;#{t}&amp;lt;/i&amp;gt;
            &amp;lt;/small&amp;gt;
          );
        })
      }
    &amp;lt;/div&amp;gt;
    &amp;lt;small&amp;gt;{createdDate}&amp;lt;/small&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;hr /&amp;gt;
  &amp;lt;Content /&amp;gt;
  &amp;lt;hr /&amp;gt;
  &amp;lt;div class="justify-between"&amp;gt;
    {
      prev &amp;amp;&amp;amp; (
        &amp;lt;a href={`/blog/${prev.slug}`}&amp;gt;
          &amp;lt;small&amp;gt;&amp;amp;larr; {prev.data.title}&amp;lt;/small&amp;gt;
        &amp;lt;/a&amp;gt;
      )
    }
    {
      next &amp;amp;&amp;amp; (
        &amp;lt;a href={`/blog/${next.slug}`}&amp;gt;
          &amp;lt;small&amp;gt;{next.data.title} &amp;amp;rarr;&amp;lt;/small&amp;gt;
        &amp;lt;/a&amp;gt;
      )
    }
  &amp;lt;/div&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should now have navigation links at the end of the blog post. You can verify that by going to &lt;code&gt;http://localhost:4321/blog/my-second-post&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may have noticed that the browser tab title always says Astro. We want this to be dynamic based on the page we are on. Introducing you to the world of &lt;strong&gt;Astro Integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Using our first Astro Integration to add SEO
&lt;/h3&gt;

&lt;p&gt;Astro provides the ability for us to use plugins either offered directly by Astro or created by the community to build things faster through Astro Integrations. We will use the &lt;code&gt;astro-seo&lt;/code&gt; integration to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fixing the title&lt;/li&gt;
&lt;li&gt;Add SEO to our page (we want the search engines to find our website)&lt;/li&gt;
&lt;li&gt;Add social tags (so our links will look when shared on Facebook, Twitter, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install &lt;code&gt;astro-seo&lt;/code&gt; 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 &lt;span class="nb"&gt;install &lt;/span&gt;astro-seo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to import &lt;code&gt;SEO&lt;/code&gt; from the &lt;code&gt;astro-seo&lt;/code&gt; integration. This component expects a few props like title, description, OG info, Twitter info, etc.&lt;/p&gt;

&lt;p&gt;Since we want to use the information corresponding to each page, we are going to define the props for our &lt;code&gt;Head.astro&lt;/code&gt; component. We are also creating an interface to get type safety.&lt;/p&gt;

&lt;p&gt;Let's create the interface first. We will create a file named &lt;code&gt;types.ts&lt;/code&gt; inside the &lt;code&gt;utils&lt;/code&gt; folder (&lt;code&gt;src\utils\types.ts&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface HeadProps {
    props: {
      title: string;
      description: string;
      image?: string | undefined;
    };
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a component named &lt;code&gt;Head.astro&lt;/code&gt; inside the &lt;code&gt;components&lt;/code&gt; folder (&lt;code&gt;src\components\Head.astro&lt;/code&gt;) with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { SEO } from "astro-seo";
import { AppConfig } from "@/utils/AppConfig";
import { type HeadProps } from "@/utils/types";

const {
  props: { title, description, image },
} = Astro.props as Props;
---

&amp;lt;SEO
  title={title || AppConfig.title}
  description={description || AppConfig.description}
  openGraph={{
    basic: {
      title: title || AppConfig.title,
      type: description || AppConfig.description,
      image: AppConfig.site + (image || AppConfig.image || ""),
    },
  }}
  twitter={{
    creator: AppConfig.twitter,
  }}
  extend={{
    link: [{ rel: "icon", href: "/favicon.svg" }],
    meta: [
      {
        name: "twitter:image",
        content: AppConfig.site + (image || AppConfig.image || ""),
      },
      { name: "twitter:title", content: title || AppConfig.title },
      {
        name: "twitter:description",
        content: description || AppConfig.description,
      },
    ],
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the &lt;code&gt;Head.astro&lt;/code&gt; component created, we want to add this to our &lt;code&gt;Base.astro&lt;/code&gt; layout page so that we will have the SEO feature applied to all the pages.&lt;/p&gt;

&lt;p&gt;We will remove the existing &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag from the &lt;code&gt;Base.astro&lt;/code&gt; file and add the &lt;code&gt;&amp;lt;Head /&amp;gt;&lt;/code&gt; component we just created. You will immediately see an error because we have to pass the mandatory props to the &lt;code&gt;&amp;lt;Head&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Again, instead of passing the values directly from the &lt;code&gt;&amp;lt;Base&amp;gt;&lt;/code&gt; layout, we will define a prop for the layout of type &lt;code&gt;HeadProps&lt;/code&gt; that we created before and have the pages that use the layout pass this information to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Head, { type HeadProps } from "@/components/Head.astro";
import Nav from "@/components/Nav.astro";

const {
  props: { title, description, image },
} = Astro.props as HeadProps;
---

&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;link rel="icon" type="image/svg+xml" href="/favicon.svg" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width" /&amp;gt;
    &amp;lt;meta name="generator" content={Astro.generator} /&amp;gt;
    &amp;lt;link rel="stylesheet" href="/css/style.css" type="text/css" /&amp;gt;
    &amp;lt;Head props={{ title, description, image }} /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;Nav /&amp;gt;
    &amp;lt;slot /&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get errors in every file that uses the &lt;code&gt;Base.astro&lt;/code&gt; file because we are not providing the value for the props. Let's do that for each page.&lt;/p&gt;

&lt;p&gt;First, let's update the &lt;code&gt;src\pages\index.astro&lt;/code&gt; (homepage). For this, we will page the values from the &lt;code&gt;AppConfig.ts&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;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";

const posts = await getCollection("blog");

const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});
---

&amp;lt;Base
  props={{
    title: AppConfig.title,
    description: AppConfig.description,
    image: AppConfig.image,
  }}
&amp;gt;
  &amp;lt;h1&amp;gt;{AppConfig.title}&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;{AppConfig.description}&amp;lt;/p&amp;gt;
  &amp;lt;h3&amp;gt;Posts&amp;lt;/h3&amp;gt;
  &amp;lt;ul&amp;gt;
    {
      sortedPosts.slice(0, 5).map((p) =&amp;gt; {
        return (
          &amp;lt;li&amp;gt;
            &amp;lt;a href={"/blog/" + p.slug}&amp;gt;{p.data.title}&amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
        );
      })
    }
  &amp;lt;/ul&amp;gt;
  &amp;lt;a href="/blog"&amp;gt;Click here&amp;lt;/a&amp;gt; to view the archive.
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's fix the blog listing page &lt;code&gt;src\pages\blog\index.astro&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";

const posts = await getCollection("blog");

const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});
---

&amp;lt;Base
  props={{
    title: "My collection of essays",
    description: AppConfig.description,
    image: AppConfig.image,
  }}
&amp;gt;
  &amp;lt;h1&amp;gt;Posts&amp;lt;/h1&amp;gt;
  &amp;lt;ul&amp;gt;
    {
      sortedPosts.map((p) =&amp;gt; {
        return (
          &amp;lt;li&amp;gt;
            &amp;lt;a href={"/blog/" + p.slug}&amp;gt;{p.data.title}&amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
        );
      })
    }
  &amp;lt;/ul&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's fix the &lt;code&gt;About&lt;/code&gt; page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";
---

&amp;lt;Base
  props={{
    title: "About | " + AppConfig.author,
    description: AppConfig.description,
    image: AppConfig.image,
  }}
&amp;gt;
  &amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we will fix the blog slug file &lt;code&gt;src\pages\blog\[slug].astro&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection } from "astro:content";
import Base from "@/layouts/Base.astro";
import { AppConfig } from "@/utils/AppConfig";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: {
      post,
    },
  }));
}

const { post } = Astro.props;
const { title, summary, createdDate, tags } = post.data;
const { Content } = await post.render();
const posts = await getCollection("blog");

const sortedPosts = posts.sort((a, b) =&amp;gt; {
  return +new Date(b.data.createdDate) - +new Date(a.data.createdDate);
});

const index = sortedPosts.findIndex((c: any) =&amp;gt; {
  return c.slug == post.slug;
});

const prev = index == 0 ? undefined : sortedPosts[index - 1];
const next =
  index == sortedPosts.length - 1 ? undefined : sortedPosts[index + 1];
---

&amp;lt;Base props={{ title: title, description: summary, image: AppConfig.image }}&amp;gt;
  &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;
  &amp;lt;div class="justify-between"&amp;gt;
    &amp;lt;div&amp;gt;
      {
        tags.map((t) =&amp;gt; {
          return (
            &amp;lt;small&amp;gt;
              &amp;lt;i&amp;gt;#{t}&amp;lt;/i&amp;gt;
            &amp;lt;/small&amp;gt;
          );
        })
      }
    &amp;lt;/div&amp;gt;
    &amp;lt;small&amp;gt;{createdDate}&amp;lt;/small&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;hr /&amp;gt;
  &amp;lt;Content /&amp;gt;
  &amp;lt;hr /&amp;gt;
  &amp;lt;div class="justify-between"&amp;gt;
    {
      prev &amp;amp;&amp;amp; (
        &amp;lt;a href={`/blog/${prev.slug}`}&amp;gt;
          &amp;lt;small&amp;gt;&amp;amp;larr; {prev.data.title}&amp;lt;/small&amp;gt;
        &amp;lt;/a&amp;gt;
      )
    }
    {
      next &amp;amp;&amp;amp; (
        &amp;lt;a href={`/blog/${next.slug}`}&amp;gt;
          &amp;lt;small&amp;gt;{next.data.title} &amp;amp;rarr;&amp;lt;/small&amp;gt;
        &amp;lt;/a&amp;gt;
      )
    }
  &amp;lt;/div&amp;gt;
&amp;lt;/Base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, we have fixed pretty much everything. The final thing related to SEO that we need to fix is the social image. We are using the value of &lt;code&gt;image&lt;/code&gt; property from the &lt;code&gt;AppConfig.ts&lt;/code&gt; file everywhere but we don't have that image. You can add the image you want to display as a preview when sharing links. I usually take a screenshot of the homepage and use that. Once you choose the image, add it to &lt;code&gt;public\images\&lt;/code&gt; with the name &lt;code&gt;social.png&lt;/code&gt; since that's the value of &lt;code&gt;AppConfig.image&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Alright, we are almost there setting up the blog. There are a couple more things we need for the blog to be complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Create an RSS feed for the blog
&lt;/h3&gt;

&lt;p&gt;We have a blog but we need an RSS feed so that people can subscribe to our blog (yes, people still subscribe to blogs).&lt;/p&gt;

&lt;p&gt;We will use another Astro integration for this called &lt;code&gt;@astro/rss&lt;/code&gt;. Let's install it using the below 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; @astrojs/rss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a file named &lt;code&gt;rss.xml.js&lt;/code&gt; inside the &lt;code&gt;pages&lt;/code&gt; folder (&lt;code&gt;src\pages\rss.xml.js&lt;/code&gt;) with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AppConfig } from "@/utils/AppConfig";
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";

export async function GET() {
  const blog = await getCollection("blog");

  return rss({
    title: AppConfig.title,
    description: AppConfig.description,
    site: AppConfig.site,
    items: blog.map((post) =&amp;gt; ({
      title: post.data.title,
      pubDate: post.data.createdDate,
      description: post.data.summary,
      link: `/blog/${post.slug}/`,
    })),
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should also add a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; to our &lt;code&gt;Base.astro&lt;/code&gt; file that allows browsers and other apps to auto-discover the RSS feed from our website.&lt;/p&gt;

&lt;p&gt;Let's add the below line to the &lt;code&gt;Base.astro&lt;/code&gt; file just above the &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt; tag.&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="nt"&gt;&amp;lt;link&lt;/span&gt;
  &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/rss+xml"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"{AppConfig.title}"&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{`${AppConfig.site}rss.xml`}"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also have to create a sitemap and a robots.txt file so that search engines can crawl our website.&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Add Sitemap and robots.txt file
&lt;/h3&gt;

&lt;p&gt;We will use another Astro integration called &lt;code&gt;sitemap&lt;/code&gt;. Instead of running &lt;code&gt;npm install&lt;/code&gt;, we will run the below command which will both install the integration as well as auto-configure the sitemap for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add sitemap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to add our website URL to &lt;code&gt;astro.config.mjs&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;import { defineConfig } from "astro/config";
import sitemap from "@astrojs/sitemap";

// https://astro.build/config
export default defineConfig({
  site: "https://yourwebsite.com",
  integrations: [sitemap()],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify that the &lt;code&gt;sitemap-index.xml&lt;/code&gt; file gets generated by running &lt;code&gt;npm run build&lt;/code&gt; and then going to the &lt;code&gt;dist&lt;/code&gt; folder created in the root of your project.&lt;/p&gt;

&lt;p&gt;Similar to how we added a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; to the RSS feed to the &lt;code&gt;Base.astro&lt;/code&gt; layout file, we have to do the same for the &lt;code&gt;sitemap-index.xml&lt;/code&gt; file. Let's add the below line to the &lt;code&gt;src\layouts\Base.astro&lt;/code&gt; file just above the &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt; tag.&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="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"sitemap"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/sitemap-index.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's create a &lt;code&gt;robots.txt&lt;/code&gt; file inside the &lt;code&gt;public&lt;/code&gt; folder (&lt;code&gt;public\robots.txt&lt;/code&gt;) with the below content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Allow: /

Sitemap: https://&amp;lt;YOUR SITE&amp;gt;/sitemap-index.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats! If you followed the tutorial till now, you have a fully functional blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Preparing for deployment
&lt;/h3&gt;

&lt;p&gt;We have a few dummy values that we need to change before we deploy this blog.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;AppConfig.ts&lt;/code&gt; file with the right information&lt;/li&gt;
&lt;li&gt;Update the dummy URL in &lt;code&gt;astro.config.mjs&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Delete the dummy Markdown files from the &lt;code&gt;content\blog&lt;/code&gt; folder and add your blog posts. Don't forget to add the necessary frontmatter to each post.&lt;/li&gt;
&lt;li&gt;Update the social image with the image you would like &lt;code&gt;public\images\social.png&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;src\pages\about.astro&lt;/code&gt; page with details about you&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you've made these changes, you can deploy to one of the many services that provide free hosting for static websites.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; - &lt;a href="https://docs.astro.build/en/guides/deploy/vercel/"&gt;Deploy your Astro Site to Vercel | Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/"&gt;Cloudflare&lt;/a&gt; - &lt;a href="https://developers.cloudflare.com/pages/framework-guides/deploy-an-astro-site/"&gt;Astro · Cloudflare Pages docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; - &lt;a href="https://docs.netlify.com/frameworks/astro/"&gt;Astro on Netlify | Netlify Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  15. Next steps
&lt;/h3&gt;

&lt;p&gt;You have a proper blog in place right now. There are a few things you can add to this to make it better.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;src\components\footer.astro&lt;/code&gt; component and add it to the &lt;code&gt;Base.astro&lt;/code&gt; layout to make it part of every page&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;src\pages\now.astro&lt;/code&gt; page to tell your readers about what you are doing now (following &lt;a href="https://sive.rs/nowff"&gt;The /now page movement | Derek Sivers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add analytics to your website (for ex: &lt;a href="https://www.goatcounter.com/"&gt;GoatCounter – open source web analytics&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add separate collection for &lt;code&gt;notes&lt;/code&gt; where you can write short notes instead of long blog posts and make it available as part of &lt;code&gt;\notes&lt;/code&gt; URL similar to &lt;code&gt;\blog&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy coding and happy writing!&lt;/p&gt;

&lt;p&gt;This post was originally published on my blog: &lt;a href="https://kramkarthik.com/programming/create-an-astro-blog/"&gt;Create an Astro blog from scratch&lt;/a&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Firefox vs Chrome Extensions: The Experience of Building and Publishing</title>
      <dc:creator>Ramkarthik</dc:creator>
      <pubDate>Mon, 14 Dec 2020 14:37:58 +0000</pubDate>
      <link>https://forem.com/ramkarthik/firefox-vs-chrome-extensions-the-experience-of-building-and-publishing-25o7</link>
      <guid>https://forem.com/ramkarthik/firefox-vs-chrome-extensions-the-experience-of-building-and-publishing-25o7</guid>
      <description>&lt;p&gt;I recently had an idea for a small web app. And for the first time, I decided to also publish both a Firefox add-on and a Chrome extension for it. This was my experience right from building it till publishing it to the corresponding stores.&lt;/p&gt;

&lt;p&gt;The idea behind this post is to share my experience so if you, the reader, plan to publish an extension in the future, you will get an idea of what to expect.&lt;/p&gt;

&lt;p&gt;The post is structured as follows and to make it more fun, I choose a winner among Firefox and Chrome web store for each stage.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; About the app&lt;/li&gt;
&lt;li&gt; Creating the extension&lt;/li&gt;
&lt;li&gt; Debugging the extension&lt;/li&gt;
&lt;li&gt; Registration process&lt;/li&gt;
&lt;li&gt; Publishing the package&lt;/li&gt;
&lt;li&gt; Initial review&lt;/li&gt;
&lt;li&gt; Subsequent release&lt;/li&gt;
&lt;li&gt; Final wrap up&lt;/li&gt;
&lt;li&gt; Some tips if you are planning to develop an extension&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  0. About the app
&lt;/h1&gt;

&lt;p&gt;Have you ever had a link open on your browser that you wanted to send to your mobile? How do you do it? I know there are a few applications that lets you do this but I wanted something really simple.&lt;/p&gt;

&lt;p&gt;Si&lt;span id="rmm"&gt;&lt;span id="rmm"&gt;n&lt;/span&gt;&lt;/span&gt;ce we can open links by scanning a QR code, I built an app called &lt;a href="https://openthisurl.com/"&gt;Open this Url&lt;/a&gt; that generates a QR code. I created a bookmarklet (bookmark link) for this app which is a JavaScript snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;javascript:void(open('[&amp;lt;https://openthisurl.com/g?url='+encodeURIComponent(location.href)&amp;gt;](&amp;lt;https://openthisurl.com/g?url=%27+encodeURIComponent(location.href)&amp;gt;), 'Open this URL', 'width=200,height=275'))&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you add this snippet to your bookmarks, you can click on the bookmark link when you have any link open and this will create a small popup with the QR code for the link. You can then scan this link from your phone.&lt;/p&gt;

&lt;p&gt;I sent it some of my friends and all of them found it useful. But some of them didn’t know about bookmarklets so they did not know how to get this to work. I created a video explaining how it works. A few of them said “But could you just make this into a browser extension?”&lt;/p&gt;

&lt;p&gt;Since I have never published a browser extension before, I thought it would be fun to try.&lt;/p&gt;

&lt;p&gt;And I did end up creating browser extensions. They are live on both &lt;a href="https://chrome.google.com/webstore/detail/openthisurl/ebcejpedcchttps://chrome.google.com/webstore/detail/openthisurl/ebcejpedccgbmpnocggeomidamkoibbbgbmpnocggeomidamkoibbb"&gt;Chrome&lt;/a&gt; and &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/openthisurl/"&gt;Firefox&lt;/a&gt; store.&lt;/p&gt;

&lt;p&gt;While building and publishing this extension for Chrome and Firefox, I started noticing the differences between the two.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Creating the extension
&lt;/h1&gt;

&lt;p&gt;Having never created an extension before, I did what most developers do when they are new to something: Copy from a tutorial, make it work and then understand what is going on to fix the issues that popup.&lt;/p&gt;

&lt;p&gt;Firefox has the best &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension"&gt;tutorial for creating an extension&lt;/a&gt;. I followed it and got my app to work a testable stage in a very short time.&lt;/p&gt;

&lt;p&gt;My assumption was that an extension I build for Firefox should work for Chrome without any major changes. And luckily that was the case. Though I did have to make some changes but more on that later.&lt;/p&gt;

&lt;p&gt;It actually took more time for me to create the required icons for the extension. Ideally you want to have a 16×16, 48×48 and 128×128 icons.&lt;/p&gt;

&lt;p&gt;Since the same code was working fine for both the browsers, this is a tie.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Debugging the extension
&lt;/h1&gt;

&lt;p&gt;Both Firefox and Chrome has a way to load extensions and test.&lt;/p&gt;

&lt;p&gt;To debug an extension in Firefox:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Open a new tab and type about:debugging&lt;/li&gt;
&lt;li&gt; Click on “This Firefox” on the left&lt;/li&gt;
&lt;li&gt; Click on Load Temporary Add-on&lt;/li&gt;
&lt;li&gt; Choose any file from your source code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To debug an extension in Chrome:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Click on the extensions icon new the URL bar to the right OR click on the three vertical dots → More tools → Extensions&lt;/li&gt;
&lt;li&gt; Toggle developer mode ON&lt;/li&gt;
&lt;li&gt; Click on Load unpacked&lt;/li&gt;
&lt;li&gt; Choose the main folder of your source code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At a high level, there are some common steps. BUT… For Firefox, I had to Google to find how to get to the extensions page whereas it was quite obvious for Chrome. That being said, once I knew how to do it in Firefox the first time, it became very easy the subsequent times.&lt;/p&gt;

&lt;p&gt;When testing the extension, I had some issues in my code and this was extremely easy to debug in Firefox. In the Extensions page, there is an icon to Inspect that gives you access to developer console for the extension. Maybe it is just me, but I had a hard time finding it in Chrome and I gave up without searching because I was able to anyway debug in Firefox.&lt;/p&gt;

&lt;p&gt;I think Firefox wins the debugging process over Chrome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox 1–0 Chrome.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Registration process
&lt;/h1&gt;

&lt;p&gt;Now that I had my extensions ready, it was time to first register for Chrome web store and Firefox add-on developer accounts.&lt;/p&gt;

&lt;p&gt;I started registering for Chrome first. There is a one time $5 registration fee. There is also a limit of 20 extensions. So if you want to publish more than 20 (which is rare for most people), you have to register a different account.&lt;/p&gt;

&lt;p&gt;During the registration process, partly because there is a fee, you have to fill lots of details. It took me around 15 minutes.&lt;/p&gt;

&lt;p&gt;Next I went to Firefox Add-on registration. This process was so quick. There is no fee and there is no limit (I could find) for the number of add-ons you can publish. The whole process took less than 5 minutes.&lt;/p&gt;

&lt;p&gt;Considering that there is a (very small) fee and it took longer to register for Chrome web store, I think Firefox wins this process as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox 2–0 Chrome.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Publishing the package
&lt;/h1&gt;

&lt;p&gt;I got both the packages ready for publishing. Both Chrome and Firefox expect you to upload a ZIP file.&lt;/p&gt;

&lt;p&gt;I started with Chrome. Like their registration process, they ask for a lot of details before you can publish your extension. They ask you to upload at least one screenshot and it has to be either 1280×800 or 640×400. Easy right? Well, not really. I created a screenshot of size 1280×800 and it gave me an error without much details (“an error occurred”). I tried a few things and after 10 minutes, it accepted one of the screenshots. I still have no idea what it expects. I read their image requirements and couldn’t find anything that I was not following during the first few tries.&lt;/p&gt;

&lt;p&gt;The whole process for Chrome took around 20 minutes.&lt;/p&gt;

&lt;p&gt;Next up, Firefox. The first thing I noticed is that when I uploaded the zip file, it said it could not recognize the extension files. The way I had my extension package was, I had the project folder as parent and then the files inside. Chrome detected this but Firefox required me to compress the files directly and not the project folder. This seemed odd but it was a quick fix.&lt;/p&gt;

&lt;p&gt;Firefox doesn’t ask for much information. It was very straightforward and took less than 10 minutes.&lt;/p&gt;

&lt;p&gt;Publishing the package was also much easier in Firefox, so it wins this round.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox 3–0 Chrome&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Initial Review
&lt;/h1&gt;

&lt;p&gt;I submitted both the extensions to their respective stores and now it was time to wait. Chrome approved my initial version first. It took them around 12 hours. There wasn’t any review comments or feedback. I was happy to see it approved in the first try.&lt;/p&gt;

&lt;p&gt;Firefox team got back in the next few hours. The extension was not approved. They wanted me to add an opt-in step at the beginning. They also wanted me to add a privacy policy link that explained what the extension does with the data. Since my extension doesn’t store any information, I mentioned that in the privacy policy page.&lt;/p&gt;

&lt;p&gt;The Firefox reviewer &lt;a href="https://blog.mozilla.org/addons/2016/07/15/writing-an-opt-in-ui-for-an-extension/"&gt;gave me a link&lt;/a&gt; that had sample implementation. This was really helpful and I was able to make those changes very quickly. I submitted it again and this time, they approved within 4 hours.&lt;/p&gt;

&lt;p&gt;For this stage, it is hard to pick a winner because both Chrome and Firefox did similar but since Firefox team asked me to include opt-in, it makes me think they value their users privacy more. So even though it took more time, I think Firefox wins this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox 4–0 Chrome&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Subsequent release
&lt;/h1&gt;

&lt;p&gt;I wanted to add few things so I had to make a few changes to the extensions. I created a new package with the changes and submitted them at the same time to both Firefox and Chrome web store.&lt;/p&gt;

&lt;p&gt;This time it took less than 4 hours for Firefox to approve and it took more than 36 hours for Chrome to approve. I’m not sure if this was just a timing issue considering the Chrome team probably gets more submissions in a day. Since they both took around the same time for the first release, I’m going to call this a tie till I submit a few more times and have enough data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tie. Firefox 4–0 Chrome.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Final wrap up
&lt;/h1&gt;

&lt;p&gt;This feels so one sided at this point but in each stage there’s not really a huge difference. But Firefox add-on store edges Chrome web store in most of the stages for me.&lt;/p&gt;

&lt;p&gt;Firefox add-on store wins 4–0 over Chrome web store.&lt;/p&gt;

&lt;h1&gt;
  
  
  8. Tips if you are planning to develop an extension
&lt;/h1&gt;

&lt;p&gt;This is what I learned from building the extension and will take forward when I build extensions in the future:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Test the extension primarily with Firefox. It has so much better debugging and an easy way to load and unload extensions.&lt;/li&gt;
&lt;li&gt; Add user opt-in along with privacy policy for your extension to avoid rejection the first time.&lt;/li&gt;
&lt;li&gt; Create high quality screenshot images to avoid frustration with Chrome.&lt;/li&gt;
&lt;li&gt; Be aware that it will cost you a one-time $5 registration fee for Chrome web store.&lt;/li&gt;
&lt;li&gt; When you package the extension into a zip file, select all the individual files and folders and compress instead of compressing the parent project folder to avoid errors during Firefox package upload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are planning to write your first extension, I would love to help you. Please reach out to me on Twitter: &lt;a href="https://twitter.com/Ramkarthik"&gt;@Ramkarthik&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have some time, I would love to hear your thoughts on my app: &lt;a href="https://openthisurl.com/"&gt;Open this URL&lt;/a&gt; You can get it for both &lt;a href="https://chrome.google.com/webstore/detail/openthisurl/ebcejpedcchttps://chrome.google.com/webstore/detail/openthisurl/ebcejpedccgbmpnocggeomidamkoibbbgbmpnocggeomidamkoibbb"&gt;Chrome&lt;/a&gt; and &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/openthisurl/"&gt;Firefox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class="kq hc bn kr ks kt"&gt;&lt;/span&gt;&lt;span class="kq hc bn kr ks kt"&gt;&lt;/span&gt;&lt;span class="kq hc bn kr ks"&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://kramkarthik.com/building-firefox-chrome-extension/"&gt;&lt;em&gt;https://kramkarthik.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 14, 2020.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>100daysofcode</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Choose a Tech Stack for my Side Projects</title>
      <dc:creator>Ramkarthik</dc:creator>
      <pubDate>Sat, 05 Dec 2020 16:04:45 +0000</pubDate>
      <link>https://forem.com/ramkarthik/how-i-choose-a-tech-stack-for-my-side-projects-586h</link>
      <guid>https://forem.com/ramkarthik/how-i-choose-a-tech-stack-for-my-side-projects-586h</guid>
      <description>&lt;p&gt;When I start a new side project, I often find myself asking the question: “What tech stack should I use for my side project?”&lt;/p&gt;

&lt;p&gt;Over the years, I have found this approach to work the best for me. I ask myself this question: What do I want from this project?&lt;/p&gt;

&lt;p&gt;If I want to get it out soon and/or make money from it, I choose the tech stack I’m most comfortable with.&lt;/p&gt;

&lt;p&gt;If it is to learn something new, I choose a somewhat unfamiliar stack. I have also found that it works best for me when I limit new parts of the tech stack to maximum two. If there are more than two unfamiliar things, I find it hard to make progress and I give up eventually.&lt;/p&gt;

&lt;p&gt;That being said, at the end of the day, I get more done by starting than mulling over what tech stack to use.&lt;/p&gt;

&lt;p&gt;If you are reading this, I would love to know how you choose a tech stack for your side projects.&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>programming</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
