<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ blog - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ blog - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 16 May 2026 22:22:45 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/blog/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Table of Contents for Your Article ]]>
                </title>
                <description>
                    <![CDATA[ When you create an article, such as a blog post for freeCodeCamp, Hashnode, Medium, or DEV.to, you can help guide the reader by creating a Table of Contents (ToC). In this article, I'll explain how to ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-table-of-contents-for-your-article/</link>
                <guid isPermaLink="false">69b27bc5f22e712aaa45f840</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ devtools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Thu, 12 Mar 2026 08:39:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/ff72c490-a57b-46c4-b0d9-8c2654853b7c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you create an article, such as a blog post for freeCodeCamp, Hashnode, Medium, or DEV.to, you can help guide the reader by creating a <a href="https://en.wikipedia.org/wiki/Table_of_contents">Table of Contents</a> (ToC). In this article, I'll explain how to create one with the help of JavaScript and browser DevTools. The article will explain how to use Google Chrome Dev Tools. But the same can be applied to any modern browser.</p>
<p>The process in this article needs to be done once per platform. Once you have the code, you can apply it every time to create a ToC. Note that if the platform changes something, you may need to adjust the script.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-browser-dev-tools">Browser Dev Tools</a></p>
</li>
<li><p><a href="#heading-javascript-console">JavaScript Console</a></p>
</li>
<li><p><a href="#heading-understanding-the-dom-structure">Understanding the DOM Structure</a></p>
</li>
<li><p><a href="#heading-creating-toc-in-markdown">Creating TOC in Markdown</a></p>
</li>
<li><p><a href="#heading-how-to-create-an-html-toc">How to create an HTML TOC?</a></p>
</li>
<li><p><a href="#heading-copy-the-html-code-for-the-editor">Copy the HTML code for the editor</a></p>
</li>
<li><p><a href="#heading-what-to-do-if-i-dont-have-headers">What to do if I don’t have headers?</a></p>
<ul>
<li><a href="#heading-create-table-of-contents-for-devto">Create Table of Contents for</a> <a href="http://DEV.to">DEV.to</a></li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-browser-dev-tools">Browser Dev Tools</h2>
<p>Dev Tools is an extension to the browser that can allow you to inspect and manipulate the DOM (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">Document Object Model</a>), which is a representation of the HTML the browser keeps in memory in the form of a tree. It also gives access to the JavaScript console, where you can write short code snippets to test something. It has a lot more features, but we'll only use those two.</p>
<p>To open Dev Tools (in Google Chrome), you can press F12 or right-click on the page with your mouse and click Inspect.</p>
<div>
<div>⚠</div>
<div>In Safari, the browser Dev Tools are disabled initially. To enable it, read: <a target="_self" rel="noopener" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer eVNpHGjtxRBq_gLOfGDr LQNqh2U1kzYxREs65IJu" href="https://support.apple.com/guide/safari/use-the-developer-tools-in-the-develop-menu-sfri20948/mac" style="pointer-events:none">Use the developer tools in the Develop menu in Safari on Mac</a>.</div>
</div>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763748137160/7e4df24f-6d25-4d43-a67b-c671bd85789a.png" alt="A browser window split in half. One the right there is an illustration of the laptop with FreeCodeCamp article on the right there is browser DevTools with DOM Tree and CSS panel." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Above is the screenshot of DevTools with a preview of this article. On the right, you can see a selected <code>h1</code> HTML tag (the title) and CSS applied to that tag. The tree structure you see is the DOM.</p>
<div>
<div>💡</div>
<div>When creating a ToC for <strong>freeCodeCamp,</strong> you should open the preview in a new tab.</div>
</div>

<h2 id="heading-javascript-console">JavaScript Console</h2>
<p>We will need to have access to the JavaScript console. To open the console in Google Chrome, you can use F12, right-click on the page and select Inspect from the context menu, or use the shortcut CTRL+SHIFT+C (Windows, Linux) or CMD+OPTION+C (Mac).</p>
<p>In Chrome DevTools, you can pick the Console tab at the top of the DevTools. But this will hide the DOM tree. It’s better to open the bottom drawer. You need to click the 3 dots in the top right corner and pick “show console drawer”.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749509540/7968ace9-624e-4037-b09a-fe298ba9b865.png" alt="Screenshot of a menu whic hallow docking the dev tools to the right, left, bottom, or in standalone window." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The Dev Tools will look like this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749614077/640a2467-ac85-4788-9836-3431a1c503bb.png" alt="Screenshot of Browser DevTools showing DOM Tree, CSS panel, and Console Drawer." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<div>
<div>💡</div>
<div>You can ignore any errors or warnings in the console. You can click this icon 🚫 on the left side of the drawer, and it will clear the console.</div>
</div>

<p>The console is a so-called <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">Read-Eval-Print-Loop</a>. A classic interface, where you type some commands, here JavaScript code, and when you press enter, the code is executed in the context of the page the DevTools is on.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749997534/b61f8cc0-62eb-4586-9898-d41a7519cf3e.png" alt="Screenshot which shows browser alert popup and JavaScript code in DevTools console which open the alert." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Above, you can see a page alert executed from the console.</p>
<h2 id="heading-understanding-the-dom-structure">Understanding the DOM Structure</h2>
<p>The first step to create a ToC is to inspect the DOM and find the headers. They are usually <strong>H1…H6</strong> tags. H1 is often the title of the page. In an ideal world, it would always be.</p>
<p>In my case, the header looks like this:</p>
<pre><code class="language-xml">&lt;h2 id="heading-dev-tools"&gt;Dev Tools&lt;/h2&gt;
</code></pre>
<p>The article only has H2 tags, but later in the article, I will also explain how to create a nested ToC.</p>
<div>
<div>💡</div>
<div>Your headers need to have an “id” attribute. It can look different, for example, be on a different element, but it has to be in the DOM. Later in the article, I will explain a few different structures and how to handle them.</div>
</div>

<p>Now with DevTools, we can write code that will find every header:</p>
<pre><code class="language-javascript">document.querySelectorAll('h2[id], h3[id], main h4[id]');
</code></pre>
<p>In the case of my article on freeCodeCamp, it returned this output:</p>
<pre><code class="language-plaintext">NodeList(5)&nbsp;[h2#heading-dev-tools, h2#heading-javascript-console, h2#heading-understanding-the-dom-structure, h2#trending-guides.col-header, h2#mobile-app.col-header]
</code></pre>
<p>First, it’s a NodeList that we need to convert to an Array. Second is that besides our headers that we have so far, we also have two headers that are part of the website and not the main content. So we need to find out the single element that is the parent of the headers we need.</p>
<p>You can right-click on the white page that contains the article and pick <strong>Inspect Element</strong>. In our case, it found an element <code>&lt;main&gt;</code>. So we can rewrite our selector as:</p>
<pre><code class="language-javascript">document.querySelectorAll('main h2[id], main h3[id], main h4[id]');
</code></pre>
<p>And now it returns our headers and nothing more.</p>
<div>
<div>💡</div>
<div>The <code>[id]</code> attribute selector is not needed here, actually. At least not on freeCodeCamp.</div>
</div>

<h2 id="heading-how-to-create-the-toc-in-markdown">How to Create the ToC in Markdown</h2>
<p>A lot of blogging platforms support Markdown, so it'll be the first thing we'll create.</p>
<p>First, we'll convert the Node list to an array. We can use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">spread operator</a>:</p>
<pre><code class="language-javascript">[...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];
</code></pre>
<p>Then we can map over the array and create the Markdown links that point to the given header.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    const hash = node.getAttribute('id');
    const indent = ' '.repeat(level * 2);
    return `\({indent}* [\){node.innerText}](#${hash})`;
});
</code></pre>
<p>The output looks like this:</p>
<pre><code class="language-plaintext">(4)&nbsp;['* [Dev Tools](#heading-dev-tools)', '* [JavaScript Console](#heading-javascript-console)', '* [Understanding the DOM Structure](#heading-understanding-the-dom-structure)', '* [What to do if I don’t have headers?](#heading-what-to-do-if-i-dont-have-headers)']
</code></pre>
<p>To get the text, we can join the array with a newline character and use <code>console.lo</code>g to display the output. If we don’t use <code>console.log</code>, it will show a string with <code>\n</code> characters.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

console.log(headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    const hash = node.getAttribute('id');
    const indent = ' '.repeat(level * 2);
    return `\({indent}* [\){node.innerText}](#${hash})`;
}).join('\n'));
</code></pre>
<p>The output for this article will look like this:</p>
<pre><code class="language-markdown">* [Dev Tools](#heading-dev-tools)
* [JavaScript Console](#heading-javascript-console)
* [Understanding the DOM Structure](#heading-understanding-the-dom-structure)
* [Creating TOC in Markdown](#heading-creating-toc-in-markdown)
  * [This is fake header](#heading-this-is-fake-header)
</code></pre>
<p>I created one fake subheader. Platforms, even when not supporting Markdown when writing articles, often support Markdown when copy-pasted. The ToC at the top of the article was created by copying and pasting markdown generated with the last JavaScript snippet.</p>
<h2 id="heading-how-to-create-an-html-toc">How to Create an HTML ToC</h2>
<p>If your platform doesn’t support Markdown (like Medium), you can create HTML, preview that HTML, and copy the output to the clipboard. Pasting that into the editor of the platform you're using should keep the formatting.</p>
<div>
<div>💡</div>
<div>On Medium, the content is inside a <code>&lt;section&gt;</code> element, so the selector must be updated.</div>
</div>

<p>To convert Markdown to HTML, you can use any online tool, but you'll see how to create it yourself in the snippet. It will be faster after you create the code.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')]

function indent(state) {
    return ' '.repeat((state.level - 1) * 2);
}

function closeUlTags(state, targetLevel) {
    while (state.level &gt; targetLevel) {
        state.level--;
        state.lines.push(`${indent(state)}&lt;/ul&gt;`);
    }
}

function openUlTags(state, targetLevel) {
    while (state.level &lt; targetLevel) {
        state.lines.push(`${indent(state)}&lt;ul&gt;`);
        state.level++;
    }
}

const result = headers.reduce((state, node) =&gt; {
    const level = parseInt(node.nodeName.replace('H', ''));

    closeUlTags(state, level);
    openUlTags(state, level);
    
    const hash = node.getAttribute('id');
    state.lines.push(`\({indent(state)}&lt;li&gt;&lt;a href="#\){hash}"&gt;${node.innerText}&lt;/a&gt;&lt;/li&gt;`);
    return state;
}, { lines: [], level: 1 });

closeUlTags(result, 1);

console.log(result.lines.join('\n'));
</code></pre>
<p>This is the output of the code in this article:</p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;&lt;a href="#heading-table-of-contents"&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-dev-tools"&gt;Dev Tools&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-javascript-console"&gt;JavaScript Console&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-understanding-the-dom-structure"&gt;Understanding the DOM Structure&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-creating-toc-in-markdown"&gt;Creating TOC in Markdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-how-to-create-html-toc"&gt;How to create HTML TOC&lt;/a&gt;&lt;/li&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href="#heading-level-3"&gt;Level 3&lt;/a&gt;&lt;/li&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#heading-level-4"&gt;Level 4&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;li&gt;&lt;a href="#heading-what-to-do-if-i-dont-have-headers"&gt;What to do if I don’t have headers?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>I added a few headers at the end, so you can see that it will work for any level of nested headers. Note that we also have the ToC as the first element on the list.</p>
<div>
<div>💡</div>
<div>Note that the above HTML code includes a link to the Table of Contents. This happens if you run the script again after adding the TOC. You can remove it by hand. If you want to improve the code, you can add a filter.</div>
</div>

<h2 id="heading-copy-the-html-code-for-the-editor">Copy the HTML code for the editor</h2>
<p>Most so-called <a href="https://en.wikipedia.org/wiki/WYSIWYG">WYSIWYG</a> editors are using HTML, and you should be able to copy the output of HTML code with formatting and paste it into that editor. The easiest is to just save that into a file, open that file, and select the text:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763758802247/7e4fa0cd-377d-44ca-9cdb-b53ec14da4b8.png" alt="Screenshot of the browser window with file open. The page in the browser shows the table of content where all text is highlighted by selection." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-what-to-do-if-i-dont-have-headers">What to Do If I Don’t Have Headers?</h2>
<p>You need to find anything that can be targeted with CSS. If they are <code>p</code> tags with a specific class (like header), you can use <code>p.header</code> instead of <code>h2</code>.</p>
<h3 id="heading-how-to-create-a-table-of-contents-for-devto">How to Create a Table of Contents for DEV.to</h3>
<p>If you have a different DOM structure, you can use different DOM methods to extract the element you need. For example, on DEV.to, the headers look like this:</p>
<pre><code class="language-xml">&lt;h2&gt;
  &lt;a name="overview" href="#overview"&gt;
  &lt;/a&gt;
  Overview
&lt;/h2&gt;
</code></pre>
<p>So the selector needs to be just <code>main h2</code>. But when you execute this code:</p>
<pre><code class="language-javascript">[...document.querySelectorAll('main h2, main h3, main h4')];
</code></pre>
<p>You will see that there are way more headers than the content of the document. Luckily, we can use a new selector in CSS <code>:has()</code>. The final selector for one header can look like this: <code>main h2:has(a[name])</code>.</p>
<p>Here is the full code:</p>
<pre><code class="language-javascript">const selector = 'main h2:has(a[name]), main h3:has(a[name]), main h4:has(a[name])';
const headers = [...document.querySelectorAll(selector)];

console.log(headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    // this is how you get the hash
    // you can also access href attribute and remove # from the output string
    const hash = node.querySelector('a').getAttribute('name');
    const indent = ' '.repeat(level);
    return `\({indent}* [\){node.innerText}](#${hash})`;
}).join('\n'));
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating a table of contents can help your readers digest your article. Since most people don’t read the whole article, they only scan for what they need. You can also find a lot of articles about its impact on SEO. So it’s always worth adding one if the article is longer.</p>
<p>And as you can see, creating a ToC is not that hard with a bit of web development knowledge.</p>
<p>If you like this article, you may want to follow me on Social Media: (<a href="https://x.com/jcubic">Twitter/X</a>, <a href="https://github.com/jcubic">GitHub</a>, and/or <a href="https://www.linkedin.com/in/jakubjankiewicz/">LinkedIn</a>). You can also check my <a href="https://jakub.jankiewicz.org/">personal website</a> and my <a href="https://jakub.jankiewicz.org/blog/">new blog</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Blog with the Ghost API and Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Ghost CMS is a popular content management system that many devs and companies use to host their blogs.  It has many features and an editor that's highly optimized for writing. You can even build different themes using handlebars.js. But if you don't ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-blog-website-with-ghost-api-and-nextjs/</link>
                <guid isPermaLink="false">66d0389b41966f84606807e8</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ghost ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rajdeep Singh ]]>
                </dc:creator>
                <pubDate>Thu, 13 Apr 2023 21:14:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/Ghost-API-and-Nextjs--2-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Ghost CMS is a popular content management system that many devs and companies use to host their blogs. </p>
<p>It has many features and an editor that's highly optimized for writing. You can even build different themes using <strong><a target="_blank" href="https://handlebarsjs.com/">handlebars</a>.js</strong>.</p>
<p>But if you don't know Handlebars, learning it can be a long and difficult process. If you are already a Next.js developer and you don't know Handlebars, creating a new theme for your Ghost-based site can be tough.</p>
<p>In the article, I will teach you how to use Ghost CMS as a backend and Next.js as a frontend. I will guide you through everything related to <a target="_blank" href="https://beta.nextjs.org/docs/getting-started">Nextjs 13 app directory</a> and the Ghost CMS API. </p>
<p>Next.js 13 team currently working on the experimental app folder. Next uses file-based routing with the <code>page</code> directory. The new <code>app</code> directory is based on file system routing and provides additional functionality like layouts, error handling, component loading, and server-side and client-side rending out of the box.</p>
<p>All the code is available on <a target="_blank" href="https://github.com/officialrajdeepsingh/nextjsghostcms">GitHub</a>. You can also check out the live <a target="_blank" href="https://nextjsghostcms.vercel.app/">demo website</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-why-use-nextjs-for-the-front-end-and-not-a-ghost-cms-theme">Why Use Next.js for the Front End and Not a Ghost CMS Theme?</a></li>
<li><a class="post-section-overview" href="#heading-project-requirements">Project Requirements</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-ghost-cms">How to Set Up Ghost CMS</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-ghost-cms-with-the-cloud">How to Set Up Ghost CMS with the Cloud</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-the-blog-template">How to Get the Blog Template</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-nextjs">How to Set Up Next.js</a></li>
<li><a class="post-section-overview" href="#what-to-know-before-following-this-tutorial-">What to know before following this tutorial</a></li>
<li><a class="post-section-overview" href="#heading-folder-structure">Folder Structure</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-ghost-cms-and-nextjs">How to Configure Ghost CMS and Next.js</a></li>
<li><a class="post-section-overview" href="#heading-understanding-the-nextjs-13-app-folder">Understanding the Next.js 13 App Folder</a></li>
<li><a class="post-section-overview" href="#heading-demo-data-for-the-project">Demo Data for the Project</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-blog">How to Build the Blog</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-header">How to Build the Header</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-footer">How to Build the Footer</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-layout">How to Build the Layout</a> </li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-homepage">How to Build the Homepage</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-reading-page">How to Build the Reading Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-tag-page">How to Build the Tag Page</a> </li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-author-page">How to Build the Author Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-single-pages">How to Build Single Pages</a></li>
<li><a class="post-section-overview" href="#heading-how-to-handle-pagination">How to Handle Pagination</a></li>
<li><a class="post-section-overview" href="#heading-nextjs-seo">Next.js SEO</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enable-search">How to Enable Search</a></li>
<li><a class="post-section-overview" href="#heading-error-handling">Error Handling</a></li>
<li><a class="post-section-overview" href="#heading-how-to-rebuild-your-static-site-with-webhooks">How to Rebuild Your Static Site with Webhooks</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<p>In this article, we cover the basics of Next's experimental app directory. Then I'll teach you how to step up Next and Ghost CMS locally and how to integrate Ghost with Next. Lastly, I'll show you how to consume data from the backend (via theGhost CMS API ) and show it on the site with React.js.</p>
<h2 id="heading-why-use-nextjs-for-the-front-end-and-not-a-ghost-cms-theme">Why Use Next.js for the Front End and Not a Ghost CMS Theme?</h2>
<p>There are a few reasons why you might consider using Next as the frontend framework for your blog:</p>
<ol>
<li>Ghost CMS doesn't generate static builds, but Next.js does.</li>
<li>You get increased website speed and performance with Next.js and it now provides built-in SEO support and other optimizations. Ghost doesn't have some of these features.</li>
<li>For React developers, it is easy to build a new blog with Next (since Next is React-based), and you do not need to learn additional tools.</li>
<li>You'll find a few service providers available for Ghost to deploy a Ghost blog with one click. Most of them come with a paid plan while one or two offer a free plan (but these tend to have time and feature limitations). For Next.js, many players are available in the market.</li>
</ol>
<p>Basically, when it comes to static builds and website performance, Ghost doesn't perform as well in either case. The alternative is to use a frontend platform like Next, React, Angular, or Vue.</p>
<p>I chose Next because it's a highly in-demand and popular React framework, and plenty of tools and libraries are built around it. </p>
<p>Note that the current project is not ready for TypeScript, but I'm working on it. Because of this <a target="_blank" href="https://medium.com/frontendweb/basic-explanation-about-the-next-config-js-file-eaa539e1fea3">I disabled TypeScript during build time</a> like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/** @type {import('next').NextConfig} */</span>
<span class="hljs-keyword">const</span> nextConfig = {
  experimental: {
    appDir: <span class="hljs-literal">true</span>,
  },

  typescript: {
    ignoreBuildErrors: <span class="hljs-literal">false</span>,
  },

}

<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = nextConfig
</code></pre>
<h2 id="heading-project-requirements">Project Requirements</h2>
<p>To follow along with this tutorial, you'll need basic knowledge of the following packages:</p>
<ol>
<li><a target="_blank" href="https://pnpm.io/">PNPM</a> is a Node.js package manager similar to npm or yarn (you can use any of them that you prefer).</li>
<li><a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a> helps you write type-safe code in JavaScript, and can also help improve productivity. It is not required, though. You can use JavaScript in your project.</li>
<li><a target="_blank" href="https://react.dev/">React.js</a> is a free and open-source front-end JavaScript library for building user interfaces with class and function components.</li>
<li><a target="_blank" href="https://beta.nextjs.org/docs/getting-started">Next.js 13 (app)</a> is based on React and it provides additional functionality like routing, error handling, and layouts.</li>
<li><a target="_blank" href="https://ghost.org/docs/content-api/">Ghost CMS API</a> is an open-source content management system (CMS) similar to WordPress. Ghost is specifically designed and built for blogging. In this project, we'll Ghost as the backend and Next as the frontend. For communication between the backend and frontend development, we'll use the Ghost CMS API. </li>
<li><a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a> is an open source CSS-based framework similar to <a target="_blank" href="https://getbootstrap.com/">Bootstrap</a>. We'll use Tailwind CSS to design our blog website. </li>
</ol>
<h2 id="heading-how-to-set-up-ghost-cms">How to Set Up Ghost CMS</h2>
<p>The next step is installing Ghost locally, which you can do with one command. First, you need to install <code>ghost-cli</code> globally with pnpm, yarn, or npm.</p>
<pre><code class="lang-bash">pnpm add -g ghost-cli@latest

<span class="hljs-comment"># or</span>

yarn global add ghost-cli@latest

<span class="hljs-comment"># or</span>

npm install ghost-cli@latest -g
</code></pre>
<p>After installing the Ghost CLI, you can create a new Ghost blog project locally with the following command:</p>
<pre><code class="lang-bash">ghost install <span class="hljs-built_in">local</span>
</code></pre>
<p>After the blog installation is finished, you can start your local development server with the <code>ghost start</code> command and your local development serve on <code>http://localhost:2368/ghost</code>.</p>
<h3 id="heading-additional-ghost-cli-commands">Additional Ghost CLI Commands</h3>
<p>There are a few additional commands that are helpful when using the Ghost CLI:</p>
<ul>
<li><code>ghost start</code>: start your server.</li>
<li><code>ghost stop</code> : stop your running Ghost server.</li>
<li><code>ghost help</code> : check the available list of commands.</li>
</ul>
<p><strong>Note:</strong></p>
<p>Make sure your current installation directory is empty before installation. Currently, you are installing Ghost in development mode. For production, you won't follow the same steps.</p>
<h2 id="heading-how-to-set-up-ghost-cms-with-the-cloud">How to Set Up Ghost CMS with the Cloud</h2>
<p>If you run into any problems with Ghost local installation, or maybe it's too complicated and you do not have enough space on your drive, you can use a tool like <a target="_blank" href="https://www.digitalpress.blog/">digital press</a> or any other cloud service like GCP or AWS, Digital Ocean, and so on.</p>
<p>I like digital press because it comes with a free plan. Other cloud services do not provide that, which is why I suggest it.</p>
<h2 id="heading-how-to-get-the-blog-template">How to Get the Blog Template</h2>
<p>Creating a new blog from scratch can be tough. In this tutorial, we'll use a pre-build template from <a target="_blank" href="https://github.com/orgs/frontendweb3">the frontend web</a>. All templates have an open-source MIT license, so you can use them, and you don't need to set up everything.</p>
<p>I picked the <a target="_blank" href="https://github.com/frontendweb3/open-blog">Open-blog</a> template from the frontend web. </p>
<h2 id="heading-how-to-set-up-nextjs">How to Set Up Next.js</h2>
<p>Setting up Next is one of the main parts of this tutorial, where you'll spend time and energy coding, debugging, and deploying the site. </p>
<p>Here are the commands to run depending on whether you're using npx, yarn, or pnpm:</p>
<pre><code class="lang-bash">npx create-next-app@latest --experimental-app

<span class="hljs-comment"># or</span>

yarn create next-app --experimental-app

<span class="hljs-comment"># or</span>

pnpm create next-app --experimental-app
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/ghostandnextjs--1-.png" alt="create a new nextjs app." width="600" height="400" loading="lazy">
<em>create a new nextjs app.</em></p>
<p>After completing the installation process, we must install some additional Node packages for the blog.</p>
<p>These Node packages can help you speed up your development process. Make sure to install all the below packages to follow along with this guide:</p>
<h3 id="heading-node-packages-to-install">Node packages to install:</h3>
<ol>
<li><code>pnpm add @tryghost/content-api</code>(required)</li>
<li><code>pnpm add @types/tryghost__content-api</code> (required by TypeScript)</li>
<li><code>pnpm add tailwindcss postcss autoprefixer</code></li>
<li><code>pnpm add  @tailwindcss/typography</code></li>
<li><code>pnpm add react-icons</code></li>
<li><code>pnpm add date-fns</code></li>
<li><code>pnpm add next-themes</code></li>
<li><code>pnpm add @radix-ui/react-popover</code></li>
</ol>
<p>Here's what each of these packages does:</p>
<ul>
<li><a target="_blank" href="https://www.npmjs.com/package/@tryghost/content-api">@tryghost/content-api</a> package is a Ghost JavaScript Client Library for fetching <a target="_blank" href="https://ghost.org/docs/content-api/">content API</a> data.</li>
<li><a target="_blank" href="https://www.npmjs.com/package/@types/tryghost__content-api">@types/tryghost__content-api</a> package contains type definitions for @tryghost/content-api.</li>
<li>TailwindCSS, autoprefixer, and PostCSS are packages required for <a target="_blank" href="https://beta.nextjs.org/docs/styling/tailwind-css">Tailwind CSS</a>.</li>
<li><a target="_blank" href="https://tailwindcss.com/docs/typography-plugin">@tailwindcss/typography</a> package for handling dynamic typography with Tailwind CSS.</li>
<li>The <a target="_blank" href="https://www.npmjs.com/package/next-themes">next-themes</a> package enables themes like switching from dark to light mode on your site.</li>
<li>The <a target="_blank" href="https://www.npmjs.com/package/react-icons">react-icons</a> package provides lots of SVG icons for the project. This way, you do not need to download them manually.</li>
<li><a target="_blank" href="https://www.radix-ui.com/docs/primitives/components/popover#installation">@radix-ui/react-popover</a> is part of the Radix UI ecosystem. I choose the Radix popover component for the design of the search component on the site.</li>
<li><a target="_blank" href="https://www.npmjs.com/package/date-fns">date-fns</a> package helps convert your <code>published_at</code> date into a different date format. </li>
</ul>
<h2 id="heading-what-to-know-before-following-this-tutorial">What to Know Before Following This Tutorial</h2>
<p>Before building this project, I highly recommend watching some tutorials on YouTube (especially if you're a beginner with Next.js). These will help you understand some basics about the Next.js experimental app folder. </p>
<p>Every video explains the same kind of topic. If you watch each of the four videos, you have a basic idea of how the Next.js app folder works. That will make this advanced tutorial easier to follow.</p>
<h3 id="heading-vercelhttpswwwyoutubecomvercelhq"><a target="_blank" href="https://www.youtube.com/@VercelHQ">Vercel</a></h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/gSSsZReIFRk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>In this tutorial, Lee Robinson covers the basics of routing, dynamic route segments, data fetching, caching, and metadata.</p>
<h3 id="heading-sakura-devhttpswwwyoutubecomsakuradev"><a target="_blank" href="https://www.youtube.com/@SakuraDev">Sakura Dev</a></h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/6htDA6v4FPM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Sakura Dev teaches you about the difference between Next.js pages and the app folder and routing with examples.</p>
<h3 id="heading-tuomo-kankaanpaa">Tuomo Kankaanpaa</h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/xXwxEudjiAY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Tuomo Kankaanpaa teaches you about Next app folder routing, layouts, and server components. </p>
<h3 id="heading-piyush-garghttpswwwyoutubecomwatchvcbfbzvdqlis"><a target="_blank" href="https://www.youtube.com/watch?v=CBfBZvDQLis">Piyush Garg</a></h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/CBfBZvDQLis" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Piyush Garg compiles all the new Next features and converts them into a small crash course, and builds a demo project.</p>
<p>Now that you're ready to go, let's get into building our blog.</p>
<h2 id="heading-folder-structure">Folder Structure</h2>
<p>Our folder structure looks like this for our demo application:</p>
<pre><code class="lang-bash">.
├── next.config.js
├── next-env.d.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── README.md
├── search.json
├── src
│   └── app
│       ├── authors
│       │   └── [slug]
│       │       └── page.tsx
│       ├── BlogLayout.tsx
│       ├── cards.min.css
│       ├── Card.tsx
│       ├── error.tsx
│       ├── favicon.ico
│       ├── Footer.tsx
│       ├── ghost-client.ts
│       ├── globals.css
│       ├── Header.tsx
│       ├── layout.tsx
│       ├── not-found.tsx
│       ├── pages
│       │   └── [slug]
│       │       └── page.tsx
│       ├── page.tsx
│       ├── pagination
│       │   └── [item]
│       │       └── page.tsx
│       ├── Pagination.tsx
│       ├── <span class="hljs-built_in">read</span>
│       │   └── [slug]
│       │       ├── Newsletter.tsx
│       │       └── page.tsx
│       ├── Search.tsx
│       ├── SocialIcons.tsx
│       └── tags
│           └── [slug]
│               └── page.tsx
├── tailwind.config.js
└── tsconfig.json

13 directories, 30 files
</code></pre>
<h2 id="heading-how-to-configure-ghost-cms-and-nextjs">How to Configure Ghost CMS and Next.js</h2>
<p>The next step is to set up data fetching for the Ghost Content API. This is why we installed the <a target="_blank" href="https://www.npmjs.com/package/@tryghost/content-api">@tryghost/content-api</a> package above.  </p>
<p>Ghost CMS comes with two types of APIs: the first is the <a target="_blank" href="https://ghost.org/docs/content-api/">Content API</a>, and the second is the <a target="_blank" href="https://ghost.org/docs/admin-api/">Admin API</a>. For the blog, we'll use the <strong><a target="_blank" href="https://ghost.org/docs/content-api/">Content API</a>.</strong></p>
<p>Content API is a RESTful API that fetches the published content for the Ghost database. It is a read-only API. You can not call POST requests with it. </p>
<p>To configure it, we create a new file inside the <code>src/app</code> folder with <code>ghost-client.ts</code>. Inside the file, we have a new Ghost API instance. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>

<span class="hljs-keyword">import</span> GhostContentAPI <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;

<span class="hljs-comment">// Create API instance with site credentials</span>
<span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> GhostContentAPI({
  url: process.env.GHOST_URL <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
  key: process.env.GHOST_KEY <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
  version: <span class="hljs-string">"v5.0"</span>
});
</code></pre>
<p>We need the blog URL, key, and version to config the Ghost content API in Next. You can find both the URLs and Key properties in the Ghost dashboard, as well as the version value which is your current version of Ghost CMS.</p>
<p>Go to the Ghost dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/ghost-next.gif" alt="Get your KEY and URL" width="600" height="400" loading="lazy">
<em>Get your KEY and URL</em></p>
<p>Go to <code>dashboard</code> &gt; <code>settings</code> &gt; <code>integrations</code> &gt; <code>Your-intergration-id</code> and get your <code>GHOST_URL</code> and <code>GHOST_KEY</code> . Now you can copy both and paste them inside your <code>.env.local</code> file.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/next-and-ghost.png" alt="Get your Ghost_Key and Ghost_URL" width="600" height="400" loading="lazy">
_Get your <code>GHOST_KEY</code> and <code>GHOST_URL</code>_</p>
<h2 id="heading-understanding-the-nextjs-13-app-folder">Understanding the Next.js 13 App Folder</h2>
<p>There have been lots of changes in the Next.js <code>pages</code> folder and <code>app</code> folder with the release of Next.js 13. We'll discuss some important stuff now and more when we're building the app:</p>
<ol>
<li>There is no <code>_app</code> , <code>_document</code>, <code>getServerSideProps</code>, <code>getStaticProps</code>, <code>getStaticPaths</code> , <code>404</code> and <code>useRouter</code>.</li>
<li>Now it combines the <code>_app</code> and <code>_document</code> files with the <code>layout</code> file.</li>
<li><code>useRouter</code> is import from <code>next/navigation</code>.</li>
<li>The <code>404</code> file is replaced by the <code>notFound()</code> function. </li>
<li>The <code>error.tsx</code> file provides functionality like reacting to error boundaries.</li>
<li>Now the <code>index.js</code> file is replaced by <code>page.js</code>.</li>
<li>Passing dynamic route segments <code>pages/blog/[slug].js</code> is changed, and the Next app directory looks like this: <code>app/blog/[slug]/page.js</code>.</li>
</ol>
<h3 id="heading-examples">Examples</h3>
<p>To understand the Next experimental app folder, let's look at a real example:</p>
<ol>
<li><strong>tag page</strong> =&gt; <code>app/tag/[slug]/page.ts</code></li>
<li><strong>category</strong> =&gt; <code>app/tag/[slug]/page.ts</code></li>
</ol>
<p>Now you can create five files inside every route. For example, if you create a <code>tag</code> or <strong><code>category</code></strong> route in your app folder, then you can create four files inside your app route folder.</p>
<ul>
<li><code>page.ts</code> (required): it is your main file.</li>
<li><code>layout.ts</code> (optional): it helps design your layout</li>
<li><code>loading.ts</code> (optional): it creates a loading indicator with React suspense. </li>
<li><code>error.ts</code> (optional): it helps handle errors in your React app.</li>
<li><code>components</code> (optional): you can also create another component in your routes.</li>
</ul>
<p>Let's understand how the new Next.js 13 app route works with a real-life example: your tag route folder looks like this.</p>
<pre><code class="lang-typescript">app/tag/[slug]/page.ts
app/tag/[slug]/loading.ts
app/tag/[slug]/layout.ts
app/tag/[slug]/error.ts
app/tag/[slug]/my-card-component.ts
</code></pre>
<h2 id="heading-demo-data-for-the-project">Demo Data for the Project</h2>
<p>You don't have to worry about creating a demo or dummy blog post data. For your testing, You can download it from this <a target="_blank" href="https://github.com/officialrajdeepsingh/nextjsghostcms/blob/main/.github/demo-post-for-ghost.json">GitHub repository</a>.</p>
<h2 id="heading-how-to-build-the-blog">How to Build the Blog</h2>
<p>We'll go through and build each part of the blog in the following sections so you can follow along at home.</p>
<ol>
<li><a class="post-section-overview" href="#heading-how-to-build-the-header">How to build the header</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-footer">How to build the footer</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-layout">How to build the layout</a></li>
<li><a class="post-section-overview" href="#how-to-built-the-homepage">How to build the homepage</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-reading-page">How to build the reading page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-tag-page">How to build the tag page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-author-page">How to build the author page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-single-pages">How to build single pages</a></li>
<li><a class="post-section-overview" href="#heading-how-to-handle-pagination">How to handle pagination</a></li>
<li><a class="post-section-overview" href="#heading-nextjs-seo">Next.js SEO</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enable-search">How to Enable Search</a></li>
<li><a class="post-section-overview" href="#heading-error-handling">Error Handling</a></li>
<li><a class="post-section-overview" href="#heading-how-to-rebuild-your-static-site-with-webhooks">How to rebuild your static site with webhooks</a></li>
</ol>
<h3 id="heading-how-to-build-the-header">How to Build the Header</h3>
<p>The first and main part of the site is the header. First, we'll create a simple header for our demo blog. Our header will end up looking like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/header.png" alt="Header of site" width="600" height="400" loading="lazy">
<em>Design of the header</em></p>
<p>First is the logo, next comes the nav bar with various elements, and last is the icon section. All the data comes from the Ghost CMS API. You can change things inside Ghost CMS and it will reflect on the site.</p>
<p>Here's the code to build the header component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Header.tsx</span>

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> SocialIcons <span class="hljs-keyword">from</span> <span class="hljs-string">"./SocialIcons"</span>;
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Settings } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Header</span>(<span class="hljs-params">{ setting }: { setting: Settings }</span>) </span>{

  <span class="hljs-keyword">return</span> (
    &lt;header className=<span class="hljs-string">"px-2 sm:px-4 py-2.5 dark:bg-gray-900 w-full"</span>&gt;

      &lt;div className=<span class="hljs-string">"container flex flex-wrap items-center justify-between mx-auto"</span>&gt;
        {<span class="hljs-comment">/* Logo for blog */</span>}
        &lt;Link href=<span class="hljs-string">"/"</span> className=<span class="hljs-string">"flex items-center"</span>&gt;
          {setting.logo !== <span class="hljs-literal">null</span> ?
            &lt;Image
              alt={setting.title} width={<span class="hljs-number">200</span>} height={<span class="hljs-number">100</span>} src={setting.logo} className=<span class="hljs-string">"self-center text-xl font-semibold whitespace-nowrap dark:text-white"</span> /&gt;
            : setting.title}
        &lt;/Link&gt;
        &lt;div className=<span class="hljs-string">"flex md:order-2"</span>&gt;

          &lt;ul className=<span class="hljs-string">"flex flex-wrap p-4 md:space-x-8 md:mt-0 md:text-sm md:font-medium"</span>&gt;

            {
              <span class="hljs-comment">/* Blog Navigation Edit in GHOST CMS  */</span>
              setting.navigation !== <span class="hljs-literal">undefined</span> ? setting?.navigation.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> &lt;li key={item.label} className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 rounded hover:text-blue-700 dark:hover:text-blue-700 md:p-0 dark:text-white"</span>
                aria-current=<span class="hljs-string">"page"</span>&gt;
                &lt;Link href={item.url}&gt;
                  {item.label}
                &lt;/Link&gt;
              &lt;/li&gt;) : <span class="hljs-string">" "</span>

            }

          &lt;/ul&gt;

        &lt;/div&gt;
        &lt;SocialIcons setting={setting} /&gt;
      &lt;/div&gt;

    &lt;/header &gt;
  )

}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header
</code></pre>
<h3 id="heading-how-to-build-the-footer">How to Build the Footer</h3>
<p>The footer is also an important section of a blog site. It shows your important information and various helpful links. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/footer.png" alt="Design footer" width="600" height="400" loading="lazy">
<em>Design of the footer</em></p>
<p>I designed a basic footer with copyrighted text and added social icons for the site. The social icons come from the Ghost CMS API.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Footer.tsx</span>

<span class="hljs-keyword">import</span> { FaTwitter, FaFacebook } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Settings } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Footer</span>(<span class="hljs-params">{ setting }: { setting: Settings }</span>) </span>{

  <span class="hljs-keyword">return</span> (

    &lt;footer className=<span class="hljs-string">"px-2 sm:px-4 py-2.5 dark:bg-gray-900 w-full"</span>&gt;

      &lt;div className=<span class="hljs-string">"container flex flex-wrap items-center justify-between mx-auto"</span>&gt;

        &lt;Link href=<span class="hljs-string">"https://github.com/frontendweb3"</span> className=<span class="hljs-string">"flex items-center"</span>&gt;
          &lt;span className=<span class="hljs-string">"self-center text-gray-800 text-sm font-semibold whitespace-nowrap dark:text-white"</span>&gt;<span class="hljs-number">2023</span> copyright frontend web&lt;/span&gt;
        &lt;/Link&gt;

        &lt;div className=<span class="hljs-string">"flex md:order-2"</span>&gt;

          &lt;ul className=<span class="hljs-string">"flex p-4 flex-row md:space-x-8 md:mt-0 md:text-sm font-medium"</span>&gt;

            {
              setting.twitter !== <span class="hljs-literal">null</span> ? &lt;li&gt;
                &lt;Link target=<span class="hljs-string">"_blank"</span> href={<span class="hljs-string">`https://twitter.com/<span class="hljs-subst">${setting.twitter}</span>`</span>} className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 rounded hover:text-blue-700 dark:hover:text-blue-700 md:p-0 dark:text-white"</span> aria-current=<span class="hljs-string">"page"</span>&gt;
                  &lt;FaTwitter /&gt;
                &lt;/Link&gt;
              &lt;/li&gt; : <span class="hljs-string">" "</span>

            }

            {
              setting.facebook !== <span class="hljs-literal">null</span> ? &lt;li&gt;
                &lt;Link target=<span class="hljs-string">"_blank"</span> href={<span class="hljs-string">`https://www.facebook.com/<span class="hljs-subst">${setting.facebook}</span>`</span>} className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 rounded hover:text-blue-700 dark:hover:text-blue-700 md:p-0 dark:text-white "</span>&gt;
                  &lt;FaFacebook /&gt;
                &lt;/Link&gt;
              &lt;/li&gt; : <span class="hljs-string">" "</span>

            }

          &lt;/ul&gt;
        &lt;/div&gt;

      &lt;/div&gt;
    &lt;/footer&gt;

  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Footer
</code></pre>
<h3 id="heading-how-to-build-the-layout">How to Build the Layout</h3>
<p>I designed a basic layout for the blog. For building layouts in Next.js, there's a special <code>layout.tsx</code> file.</p>
<p>Before we create the layout design, we need to define a <code>getNavigation</code> function to <strong>fetch</strong> navigation and basic website-related data from Ghost. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getNavigation</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.settings.browse()
}
</code></pre>
<h4 id="heading-the-data-look-like-this">The data look like this:</h4>
<pre><code class="lang-object">{
  title: 'Rajdeep Singh',
  description: 'Thoughts, stories and ideas.',
  logo: 'http://localhost:2368/content/images/2023/04/nextjsandghostlogo-2.png',
  icon: 'http://localhost:2368/content/images/size/w256h256/2023/04/nextjs-60pxx60px.png',
  accent_color: '#d27fa0',
  cover_image: 'https://static.ghost.org/v4.0.0/images/publication-cover.jpg',
  facebook: 'ghost',
  twitter: '@ghost',
  lang: 'en',
  locale: 'en',
  timezone: 'Etc/UTC',
  codeinjection_head: null,
  codeinjection_foot: null,
  navigation: Array(5) [
    { label: 'Home', url: '/' }, { label: 'JavaScript', url: '/tags/javascript/' }, { label: 'Nextjs', url: '/tags/nextjs/' },
    { label: 'Reactjs', url: '/tags/reactjs/' }, { label: 'Ghost CMS', url: '/tags/ghost-cms/' }
  ],
  secondary_navigation: Array(1) [ { label: 'Login', url: '#/portal/' } ],
  meta_title: 'My demo post',
  meta_description: 
    'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.',
  og_image: null,
  og_title: null,
  og_description: 
    'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.',
  twitter_image: null,
  twitter_title: null,
  twitter_description: 
    'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.',
  members_support_address: 'noreply',
  members_enabled: true,
  members_invite_only: false,
  paid_members_enabled: false,
  firstpromoter_account: null,
  portal_button_style: 'icon-and-text',
  portal_button_signup_text: 'Subscribe',
  portal_button_icon: null,
  portal_plans: Array(1) [ 'free' ],
  portal_name: true,
  portal_button: true,
  comments_enabled: 'all',
  url: 'http://localhost:2368/',
  version: '5.39'
}
</code></pre>
<p>The <code>getNavigation</code> function returns the settings data, and then we pass the data as props into the header and footer components.</p>
<p>Our Main <code>layout.tsx</code> file works server side. It helps fetch data on the server side with the React <code>use</code> hook.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Layout.tsx</span>


<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">'./BlogLayout'</span>
<span class="hljs-keyword">import</span> { getNavigation, } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ghost-client"</span>
<span class="hljs-keyword">import</span> { use } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Settings } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>

<span class="hljs-keyword">interface</span> UpdateSettings <span class="hljs-keyword">extends</span> Settings {
  accent_color?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
  children,
}: {
  children: React.ReactNode
}</span>) </span>{

  <span class="hljs-keyword">const</span> settings: UpdateSettings = use(getNavigation())

  <span class="hljs-keyword">return</span> (

    &lt;html className=<span class="hljs-string">'light'</span> lang=<span class="hljs-string">"en"</span>&gt;

      &lt;body
        style={{
          <span class="hljs-string">'--bg-color'</span>: settings?.accent_color ? settings.accent_color : <span class="hljs-string">""</span>,
        }}
        className={<span class="hljs-string">` bg-[--bg-color] dark:bg-gray-900`</span>}&gt;

        &lt;BlogLayout setting={settings}&gt;

          {children}

        &lt;/BlogLayout&gt;

      &lt;/body&gt;

    &lt;/html&gt;

  )
}
</code></pre>
<h4 id="heading-bloglayout-component">BlogLayout component</h4>
<p>The <code>BlogLayout</code> component works on the client side. In the Next.js app folder, you can easily convert your server-side component to the client side with the following <code>"use client"</code> syntax.</p>
<p>The purpose of the BlogLayout component is to contain the <a target="_blank" href="https://www.npmjs.com/package/next-themes">ThemeProvider</a>, header, and footer. ThemeProvider is a high-order component, and it provides additional functionality, like changing the theme from dark to light. We wrap the intra-site with ThemeProvider's higher component. In the old pages directory, we achieve similarly functionally with  nextjs <code>_app.ts</code> custom app.</p>
<p>ThemeProvider component helps to change the theme from light to dark mode.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>

<span class="hljs-comment">// BlogLayout.tsx</span>

<span class="hljs-keyword">import</span> Footer <span class="hljs-keyword">from</span> <span class="hljs-string">"./Footer"</span>;
<span class="hljs-keyword">import</span> Header <span class="hljs-keyword">from</span> <span class="hljs-string">"./Header"</span>;
<span class="hljs-keyword">import</span> { ThemeProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-themes'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Settings } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Layout</span>(<span class="hljs-params">{ setting, children }: { setting: Settings, children: React.ReactNode }</span>) </span>{
  <span class="hljs-keyword">return</span> &lt;ThemeProvider attribute=<span class="hljs-string">"class"</span>&gt;
    &lt;Header setting={setting} /&gt;
    {children}
    &lt;Footer setting={setting} /&gt;
  &lt;/ThemeProvider&gt;

}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Layout
</code></pre>
<h3 id="heading-how-to-build-the-homepage">How to Build the Homepage</h3>
<p>Next.js has a special <code>app/page.tsx</code> file for designing and building the home page. Our blog website's home page looks like what you see below. We import the header, card, pagination, and footer on the home page. The header and footer are part of <code>layout.tsx</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Home-page-1.png" alt="Home page" width="600" height="400" loading="lazy">
<em>Home page</em></p>
<p>First, we fetch all posts data from Ghost CMS with the help of the <code>getPosts</code>  function, which I defined in the <code>ghost-client.ts</code> file. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPosts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts
    .browse({
      include: [<span class="hljs-string">"tags"</span>, <span class="hljs-string">"authors"</span>],
      limit: <span class="hljs-number">10</span>
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
    });
}
</code></pre>
<p>By default, the <code>api.post.browse()</code> returns only post data, but you can easily extend it. In every article or post data, we also include tags and authors with the help of <code>include</code>. Then we set the article limit to ten.</p>
<h4 id="heading-the-data-look-like-this-1">The data look like this:</h4>
<pre><code class="lang-json"> [
  {
    id: '<span class="hljs-number">6422</span>a742136f5d40f37294f5',
    uuid: '<span class="hljs-number">8</span>c2fcfda-a6e4<span class="hljs-number">-4383</span><span class="hljs-number">-893</span>b-ba18511c0f67',
    title: 'Demo Posts with Nextjs and Ghost Editor',
    slug: 'demo-posts-with-nextjs-and-reactjs',
    html: `&lt;p&gt;&lt;strong&gt;Lorem Ipsum&lt;/strong&gt; is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text si
nce the <span class="hljs-number">1500</span>s when an unknown printer scrambled a galley of type and scrambled it to make a type specimen book. &lt;/p&gt;&lt;p&gt;It has survived five centuries and the leap i
nto electronic typesetting, remaining essentially unchanged. &lt;/p&gt;&lt;p&gt;It was popularised in the 1960s with Letraset sheets containing Lorem Ipsum passages and, more r
ecently, desktop publishing software like Aldus PageMaker, including versions of Lorem Ipsum.&lt;/p&gt;&lt;figure class=<span class="hljs-attr">"kg-card kg-gallery-card kg-width-wide kg-card-hascap
tion"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-container"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-row"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-image"</span>&gt;&lt;img src=<span class="hljs-attr">"http://localhost:2368/content/images/2023/03/Build-and-d
eploy.png"</span> width=<span class="hljs-attr">"1500"</span> height=<span class="hljs-attr">"400"</span> loading=<span class="hljs-attr">"lazy"</span> alt srcset=<span class="hljs-attr">"http://localhost:2368/content/images/size/w600/2023/03/Build-and-deploy.png 600w, http://localhost:2
368/content/images/size/w1000/2023/03/Build-and-deploy.png 1000w, http://localhost:2368/content/images/2023/03/Build-and-deploy.png 1500w"</span> sizes=<span class="hljs-attr">"(min-width: 720px)
 720px"</span>&gt;&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-image"</span>&gt;&lt;img src=<span class="hljs-attr">"http://localhost:2368/content/images/2023/03/Build-and-deploy-profile-1.png"</span> width=<span class="hljs-attr">"1500"</span> height=<span class="hljs-attr">"400"</span> loading
=<span class="hljs-attr">"lazy"</span> alt srcset=<span class="hljs-attr">"http://localhost:2368/content/images/size/w600/2023/03/Build-and-deploy-profile-1.png 600w, http://localhost:2368/content/images/size/w1000/2023
/03/Build-and-deploy-profile-1.png 1000w, http://localhost:2368/content/images/2023/03/Build-and-deploy-profile-1.png 1500w"</span> sizes=<span class="hljs-attr">"(min-width: 720px) 720px"</span>&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-row"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-image"</span>&gt;&lt;img src=<span class="hljs-attr">"http://localhost:2368/content/images/2023/03/Build-and-deploy-profile--1--1.png"</span> width=<span class="hljs-attr">"15
00"</span> height=<span class="hljs-attr">"400"</span> loading=<span class="hljs-attr">"lazy"</span> alt srcset=<span class="hljs-attr">"http://localhost:2368/content/images/size/w600/2023/03/Build-and-deploy-profile--1--1.png 600w, http://localhost:2368/co
ntent/images/size/w1000/2023/03/Build-and-deploy-profile--1--1.png 1000w, http://localhost:2368/content/images/2023/03/Build-and-deploy-profile--1--1.png 1500w"</span> siz
es=<span class="hljs-attr">"(min-width: 720px) 720px"</span>&gt;&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-gallery-image"</span>&gt;&lt;img src=<span class="hljs-attr">"http://localhost:2368/content/images/2023/03/Build--Test-and-Deploy-profile-1.png"</span> width
=<span class="hljs-attr">"1500"</span> height=<span class="hljs-attr">"400"</span> loading=<span class="hljs-attr">"lazy"</span> alt srcset=<span class="hljs-attr">"http://localhost:2368/content/images/size/w600/2023/03/Build--Test-and-Deploy-profile-1.png 600w, http://localhost:2
368/content/images/size/w1000/2023/03/Build--Test-and-Deploy-profile-1.png 1000w, http://localhost:2368/content/images/2023/03/Build--Test-and-Deploy-profile-1.png 
1500w"</span> sizes=<span class="hljs-attr">"(min-width: 720px) 720px"</span>&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;figcaption&gt;Build and deploy&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2 id=<span class="hljs-attr">"why-do-we-use-it"</span>&gt;Why do we use it?&lt;/h2&gt;&lt;p&gt;It is
 a long-established fact that a reader will be distracted by the readable content of a page when looking at its layout. &lt;/p&gt;&lt;p&gt;The point of using Lorem Ipsum is tha
t it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. &lt;/p&gt;&lt;p&gt;Many desktop 
publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their 
infancy. &lt;/p&gt;&lt;p&gt;Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).&lt;/p&gt;&lt;hr&gt;&lt;h2 id=<span class="hljs-attr">"where-can-i
-get-some"</span>&gt;Where can I get some?&lt;/h2&gt;&lt;p&gt;There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by i
njected humour, or randomised words which don't look even slightly believable. &lt;/p&gt;&lt;p&gt;If you are going to use a passage of Lorem Ipsum, you need to be sure there is
n't anything embarrassing hidden in the middle of text. &lt;/p&gt;&lt;p&gt;All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making 
this the first true generator on the Internet. &lt;/p&gt;&lt;p&gt;It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generat
e Lorem Ipsum which looks reasonable. &lt;/p&gt;&lt;p&gt;The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.&lt;/
p&gt;&lt;div class=<span class="hljs-attr">"kg-card kg-callout-card kg-callout-card-red"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-callout-emoji"</span>&gt;💡&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-callout-text"</span>&gt;My note is here&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/
p&gt;&lt;div class=<span class="hljs-attr">"kg-card kg-header-card kg-width-full kg-size-small kg-style-dark"</span> style data-kg-background-image&gt;&lt;h2 class=<span class="hljs-attr">"kg-header-card-header"</span> id=<span class="hljs-attr">"product"</span>&gt;Produc
t&lt;/h2&gt;&lt;h3 class=<span class="hljs-attr">"kg-header-card-subheader"</span> id=<span class="hljs-attr">"my-blog-list"</span>&gt;My blog list&lt;/h3&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure class=<span class="hljs-attr">"kg-card kg-embed-card kg-card-hascaption"</span>&gt;&lt;iframe width=<span class="hljs-attr">"2
00"</span> height=<span class="hljs-attr">"113"</span> src=<span class="hljs-attr">"https://www.youtube.com/embed/_q1K7cybyRk?feature=oembed"</span> frameborder=<span class="hljs-attr">"0"</span> allow=<span class="hljs-attr">"accelerometer; autoplay; clipboard-write; encrypted-media; gy
roscope; picture-in-picture; web-share"</span> allowfullscreen title=<span class="hljs-attr">"Next.js 13.1 Explained!"</span>&gt;&lt;/iframe&gt;&lt;figcaption&gt;youtube&lt;/figcaption&gt;&lt;/figure&gt;&lt;hr&gt;&lt;figure class=<span class="hljs-attr">"kg-card
 kg-embed-card"</span>&gt;&lt;blockquote class=<span class="hljs-attr">"twitter-tweet"</span>&gt;&lt;p lang=<span class="hljs-attr">"en"</span> dir=<span class="hljs-attr">"ltr"</span>&gt;In 2022, we enabled developers to create at the moment of inspiration, now with over 2 mill
ion deployments per week.&lt;br&gt;&lt;br&gt;Here&amp;#39;s what we shipped ↓ &lt;a href=<span class="hljs-attr">"https://t.co/6k7Xmbpna3?ref=localhost"</span>&gt;pic.twitter.com/6k7Xmbpna3&lt;/a&gt;&lt;/p&gt;&amp;mdash; Vercel (@ver
cel) &lt;a href=<span class="hljs-attr">"https://twitter.com/vercel/status/1611094825587167254?ref_src=twsrc%5Etfw&amp;ref=localhost"</span>&gt;January 5, 2023&lt;/a&gt;&lt;/blockquote&gt;\n` +
      '&lt;script async src=<span class="hljs-attr">"https://platform.twitter.com/widgets.js"</span> charset=<span class="hljs-attr">"utf-8"</span>&gt;&lt;/script&gt;\n' +
      '&lt;/figure&gt;&lt;hr&gt;&lt;figure class=<span class="hljs-attr">"kg-card kg-bookmark-card kg-card-hascaption"</span>&gt;&lt;a class=<span class="hljs-attr">"kg-bookmark-container"</span> href=<span class="hljs-attr">"https://medium.com/frontendweb/what-is-progre
ssive-web-app-and-how-to-enable-it-in-nextjs-application-17f2e3240390?ref=localhost"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-bookmark-content"</span>&gt;&lt;div class=<span class="hljs-attr">"kg-bookmark-title"</span>&gt;What is Progres
sive Web App and How to enable it in nextjs Application?&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-bookmark-description"</span>&gt;A detailed guide to Progressive Web Apps: How to use it with next
js and publish on Google play store, Microsoft store, Meta Quest, and…&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-bookmark-metadata"</span>&gt;&lt;img class=<span class="hljs-attr">"kg-bookmark-icon"</span> src=<span class="hljs-attr">"https://cdn-static-
1.medium.com/_/fp/icons/Medium-Avatar-500x500.svg"</span> alt&gt;&lt;span class=<span class="hljs-attr">"kg-bookmark-author"</span>&gt;FrontEnd web&lt;/span&gt;&lt;span class=<span class="hljs-attr">"kg-bookmark-publisher"</span>&gt;Rajdeep singh&lt;/span&gt;&lt;
/div&gt;&lt;/div&gt;&lt;div class=<span class="hljs-attr">"kg-bookmark-thumbnail"</span>&gt;&lt;img src=<span class="hljs-attr">"https://miro.medium.com/v2/resize:fit:1200/1*yAoHfq4Wm2Bp8DU1Dav29Q.png"</span> alt&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Bookmark&lt;
/figcaption&gt;&lt;/figure&gt;&lt;div class=<span class="hljs-attr">"kg-card kg-header-card kg-width-full kg-size-small kg-style-dark"</span> style data-kg-background-image&gt;&lt;h2 class=<span class="hljs-attr">"kg-header-card-header"</span> 
id=<span class="hljs-attr">"thank-you"</span>&gt;Thank you&lt;/h2&gt;&lt;/div&gt;',
    comment_id: '<span class="hljs-number">6422</span>a742136f5d40f37294f5',
    feature_image: 'https:<span class="hljs-comment">//images.unsplash.com/photo-1543966888-7c1dc482a810?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE2fHxqYXZhc2Nya</span>
XB0fGVufDB8fHx8MTY3OTk5MjY1NA&amp;ixlib=rb<span class="hljs-number">-4.0</span><span class="hljs-number">.3</span>&amp;q=<span class="hljs-number">80</span>&amp;w=<span class="hljs-number">2000</span>',
    featured: <span class="hljs-literal">false</span>,
    visibility: 'public',
    created_at: '<span class="hljs-number">2023</span><span class="hljs-number">-03</span><span class="hljs-number">-28</span>T08:<span class="hljs-number">37</span>:<span class="hljs-number">22.000</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>',
    updated_at: '<span class="hljs-number">2023</span><span class="hljs-number">-03</span><span class="hljs-number">-28</span>T08:<span class="hljs-number">51</span>:<span class="hljs-number">38.000</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>',
    published_at: '<span class="hljs-number">2023</span><span class="hljs-number">-03</span><span class="hljs-number">-28</span>T08:<span class="hljs-number">50</span>:<span class="hljs-number">44.000</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>',
    custom_excerpt: 'It has survived five centuries and the leap into electronic typesetting, remaining essentially unchanged. ',
    codeinjection_head: <span class="hljs-literal">null</span>,
    codeinjection_foot: <span class="hljs-literal">null</span>,
    custom_template: <span class="hljs-literal">null</span>,
    canonical_url: <span class="hljs-literal">null</span>,
    tags: [ [Object] ],
    authors: [ [Object] ],
    primary_author: {
      id: '<span class="hljs-number">1</span>',
      name: 'Rajdeep Singh',
      slug: 'rajdeep',
      profile_image: 'https:<span class="hljs-comment">//www.gravatar.com/avatar/dafca7497609ae294378279ad1d6136c?s=250&amp;r=x&amp;d=mp',</span>
      cover_image: <span class="hljs-literal">null</span>,
      bio: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. ',
      website: 'https:<span class="hljs-comment">//officialrajdeepsingh.dev',</span>
      location: 'India',
      facebook: 'officialrajdeepsingh',
      twitter: '@Official_R_deep',
      meta_title: <span class="hljs-literal">null</span>,
      meta_description: <span class="hljs-literal">null</span>,
      url: 'http:<span class="hljs-comment">//localhost:2368/author/rajdeep/'</span>
    },
    primary_tag: {
      id: '<span class="hljs-number">6422</span>aa9a136f5d40f3729552',
      name: 'demo',
      slug: 'demo',
      description: <span class="hljs-literal">null</span>,
      feature_image: <span class="hljs-literal">null</span>,
      visibility: 'public',
      og_image: <span class="hljs-literal">null</span>,
      og_title: <span class="hljs-literal">null</span>,
      og_description: <span class="hljs-literal">null</span>,
      twitter_image: <span class="hljs-literal">null</span>,
      twitter_title: <span class="hljs-literal">null</span>,
      twitter_description: <span class="hljs-literal">null</span>,
      meta_title: <span class="hljs-literal">null</span>,
      meta_description: <span class="hljs-literal">null</span>,
      codeinjection_head: <span class="hljs-literal">null</span>,
      codeinjection_foot: <span class="hljs-literal">null</span>,
      canonical_url: <span class="hljs-literal">null</span>,
      accent_color: <span class="hljs-literal">null</span>,
      url: 'http:<span class="hljs-comment">//localhost:2368/tag/demo/'</span>
    },
    url: 'http:<span class="hljs-comment">//localhost:2368/demo-posts-with-nextjs-and-reactjs/',</span>
    excerpt: 'It has survived five centuries and the leap into electronic typesetting, remaining essentially unchanged. ',
    reading_time: <span class="hljs-number">3</span>,
    access: <span class="hljs-literal">true</span>,
    comments: <span class="hljs-literal">true</span>,
    og_image: <span class="hljs-literal">null</span>,
    og_title: <span class="hljs-literal">null</span>,
    og_description: <span class="hljs-literal">null</span>,
    twitter_image: <span class="hljs-literal">null</span>,
    twitter_title: <span class="hljs-literal">null</span>,
    twitter_description: <span class="hljs-literal">null</span>,
    meta_title: <span class="hljs-literal">null</span>,
    meta_description: <span class="hljs-literal">null</span>,
    email_subject: <span class="hljs-literal">null</span>,
    frontmatter: <span class="hljs-literal">null</span>,
    feature_image_alt: 'Demo Posts with Nextjs and Ghost Editor',
    feature_image_caption: 'Photo by &lt;a href=<span class="hljs-string">"https://unsplash.com/@pinjasaur?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"</span>&gt;Paul Esch-Laurent&lt;/a&gt; / 
&lt;a href=<span class="hljs-string">"https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"</span>&gt;Unsplash&lt;/a&gt;'
  },
meta:{
    pagination: { page: <span class="hljs-number">1</span>, limit: <span class="hljs-number">10</span>, pages: <span class="hljs-number">2</span>, total: <span class="hljs-number">12</span>, next: <span class="hljs-number">2</span>, prev: <span class="hljs-literal">null</span> }
  }
]
</code></pre>
<p>Now we call the <code>getPosts</code> function on the server side. It returns all the post data with the associated tags and authors. Now you can loop through the data with a <code>map()</code> function. </p>
<p>We pass the data into <code>app/page.tsx</code> to the <code>card.tsx</code> components. We pass the article data as props into the card component.</p>
<pre><code class="lang-typescript">
<span class="hljs-comment">// src/app/page.tsx</span>

<span class="hljs-keyword">import</span> { getPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ghost-client"</span>
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">'./Card'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> getPost = <span class="hljs-keyword">await</span> getPosts()

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;main className=<span class="hljs-string">"container my-12 mx-auto grid grid-cols-1 gap-2 md:gap-3 lg:gap-4 lg:grid-cols-3 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4"</span>&gt;

        {
          getPost?.map(
            <span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
              <span class="hljs-keyword">return</span> &lt;Card key={item.uuid} item={item} /&gt;
            })
        }

      &lt;/main&gt;

    &lt;/&gt;
  )
}
</code></pre>
<h4 id="heading-card-component">Card component</h4>
<p>I designed a basic card for the blog. The card component looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/card.png" alt="Card component" width="600" height="400" loading="lazy">
<em>Card component</em></p>
<p>I rendered every item of data coming from the home page as props and showed it on the site with <code>Card.tsx</code> .</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Card.tsx</span>

<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostOrPage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;
<span class="hljs-keyword">import</span> { format } <span class="hljs-keyword">from</span> <span class="hljs-string">'date-fns'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{ item }: { item: PostOrPage }</span>) </span>{

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"max-w-full bg-white dark:bg-gray-800"</span> &gt;

      {
        item.featured !== <span class="hljs-literal">null</span> &amp;&amp; item.feature_image !== <span class="hljs-literal">undefined</span> ? &lt;Link href={<span class="hljs-string">`/read/<span class="hljs-subst">${item.slug}</span>`</span>}&gt;
          &lt;Image className=<span class="hljs-string">"rounded-lg p-3"</span> width={<span class="hljs-number">1000</span>} height={<span class="hljs-number">324</span>} src={item.feature_image} alt={item.feature_image_alt || item.title} /&gt;
        &lt;/Link&gt; : <span class="hljs-string">" "</span>
      }

      &lt;div className=<span class="hljs-string">"p-3"</span>&gt;

        &lt;div className=<span class="hljs-string">"flex mb-3"</span>&gt;
          {
            item.published_at !== <span class="hljs-literal">null</span> &amp;&amp; item.published_at !== <span class="hljs-literal">undefined</span> ? &lt;p className=<span class="hljs-string">"text-sm text-gray-500 dark:text-gray-400"</span>&gt;
              {format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(item.published_at), <span class="hljs-string">'dd MMMM, yyyy'</span>)}
            &lt;/p&gt; : <span class="hljs-string">""</span>
          }
          &lt;p className=<span class="hljs-string">"text-sm text-gray-500 dark:text-gray-400 mx-1"</span>&gt; , &lt;/p&gt;
          &lt;p className=<span class="hljs-string">"text-sm text-gray-500 dark:text-gray-400"</span>&gt;
            {item.reading_time} min read
          &lt;/p&gt;
        &lt;/div&gt;

        &lt;Link href={<span class="hljs-string">`/read/<span class="hljs-subst">${item.slug}</span>`</span>}&gt;
          &lt;h5 className=<span class="hljs-string">"mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"</span>&gt;
            {item.title}
          &lt;/h5&gt;
        &lt;/Link&gt;


      &lt;/div&gt;

    &lt;/div&gt;

  )

}



<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Card
</code></pre>
<h3 id="heading-how-to-build-the-reading-page">How to Build the Reading Page</h3>
<p>The reading page is the second most important page for the blog site. If people can't figure out how to read what the author writes, this is a big problem for front-end developers. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghostandnext-reading.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reading page</em></p>
<p>First, we get a single article from the Ghost CMS API based on its slug. We pass it to the <code>Card</code> component with the <code>Link</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSinglePost</span>(<span class="hljs-params">postSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts
    .read({
      slug: postSlug
    }, { include: [<span class="hljs-string">"tags"</span>, <span class="hljs-string">"authors"</span>] })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(err);
    });
}
</code></pre>
<p>The <code>getSinglePost(&lt;you-slug&gt;)</code> function returns data about a single article, and you can render that data on the page.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/read/[slug]/page.tsx</span>

<span class="hljs-keyword">import</span> Newsletter <span class="hljs-keyword">from</span> <span class="hljs-string">"./Newsletter"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { getSinglePost, getPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../ghost-client"</span>
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-comment">// import icon</span>
<span class="hljs-keyword">import</span> { FaAngleLeft } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;

<span class="hljs-comment">// types for typescript</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostOrPage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;

<span class="hljs-comment">// format the date</span>
<span class="hljs-keyword">import</span> { format } <span class="hljs-keyword">from</span> <span class="hljs-string">'date-fns'</span>

<span class="hljs-comment">// css for card</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"../../cards.min.css"</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> getPosts()
  <span class="hljs-keyword">return</span> posts.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> ({
    slug: post.slug,
  }));
}


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Read</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>) </span>{

  <span class="hljs-keyword">const</span> getPost = <span class="hljs-keyword">await</span> getSinglePost(params.slug)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;main className=<span class="hljs-string">"pt-8 pb-16 lg:pt-16 lg:pb-24 dark:bg-gray-900"</span>&gt;

        &lt;div className=<span class="hljs-string">"flex justify-between px-4 mx-auto max-w-screen-xl "</span>&gt;

          &lt;article className=<span class="hljs-string">"mx-auto w-full max-w-3xl prose prose-xl prose-p:text-gray-800  dark:prose-p:text-gray-100 sm:prose-base prose-a:no-underline prose-blue dark:prose-invert"</span>&gt;

            &lt;div className=<span class="hljs-string">"flex mb-4 w-full justify-between"</span>&gt;

              &lt;Link className=<span class="hljs-string">"inline-flex items-center"</span> href={<span class="hljs-string">`/`</span>}&gt;
                &lt;FaAngleLeft /&gt; Back
              &lt;/Link&gt;

              {
                getPost.primary_tag ? &lt;Link href={<span class="hljs-string">`/tags/<span class="hljs-subst">${getPost?.primary_tag.slug}</span>`</span>}&gt;
                  # {getPost?.primary_tag.name}
                &lt;/Link&gt; : <span class="hljs-string">""</span>
              }

            &lt;/div&gt;

            &lt;h1 className=<span class="hljs-string">"mb-4 text-3xl font-extrabold leading-tight text-gray-900 lg:mb-6 lg:text-4xl dark:text-white"</span>&gt;
              {getPost.title}
            &lt;/h1&gt;

            &lt;p className=<span class="hljs-string">"lead"</span>&gt;
              {getPost.excerpt}
            &lt;/p&gt;

            &lt;header className=<span class="hljs-string">"mb-4 lg:mb-6 not-format"</span>&gt;

              &lt;address className=<span class="hljs-string">"flex items-center mb-6 not-italic"</span>&gt;

                &lt;div className=<span class="hljs-string">"inline-flex items-center mr-3 text-sm text-gray-900 dark:text-white"</span>&gt;

                  &lt;Image width={<span class="hljs-number">32</span>} height={<span class="hljs-number">32</span>} className=<span class="hljs-string">"mr-4 w-10 h-10 rounded-full"</span> src={getPost?.primary_author.profile_image} alt={getPost?.primary_author.name} /&gt;
                  {
                    getPost.primary_author ? &lt;Link href={<span class="hljs-string">`/authors/<span class="hljs-subst">${getPost?.primary_author.slug}</span>`</span>} rel=<span class="hljs-string">"author"</span> className=<span class="hljs-string">"text-xl font-bold text-gray-800 dark:text-white"</span>&gt;{getPost?.primary_author.name}&lt;/Link&gt; : <span class="hljs-string">" "</span>
                  }

                  {
                    getPost.published_at ? &lt;time className=<span class="hljs-string">"text-base font-light text-gray-800 dark:text-white mx-1"</span> dateTime={getPost?.published_at} title={format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(getPost?.published_at), <span class="hljs-string">'yyyy-MM-dd'</span>)}&gt;
                      {format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(getPost?.published_at), <span class="hljs-string">'dd MMMM, yyyy'</span>)}
                    &lt;/time&gt; : <span class="hljs-string">""</span>
                  }

                  &lt;div className=<span class="hljs-string">"text-base w-1 h-1 rounded-full bg-black dark:bg-white mx-1"</span>&gt;&lt;/div&gt;

                  &lt;p className=<span class="hljs-string">"text-base font-light text-gray-500 dark:text-gray-400"</span>&gt; {getPost.reading_time}  Min Read&lt;/p&gt;

                &lt;/div&gt;

              &lt;/address&gt;

            &lt;/header&gt;

            &lt;figure&gt;
              &lt;Image className=<span class="hljs-string">"mx-auto"</span> width={<span class="hljs-number">1000</span>} height={<span class="hljs-number">250</span>} src={getPost.feature_image} alt={getPost.feature_image_alt} /&gt;
              &lt;figcaption className=<span class="hljs-string">"text-center"</span>
                dangerouslySetInnerHTML={{
                  __html: getPost?.feature_image_caption
                }}&gt;&lt;/figcaption&gt;
            &lt;/figure&gt;

            &lt;div dangerouslySetInnerHTML={{ __html: getPost?.html }}&gt;&lt;/div&gt;

          &lt;/article&gt;
        &lt;/div&gt;
      &lt;/main&gt;
      &lt;Newsletter /&gt;
    &lt;/&gt;
  )

}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Read
</code></pre>
<p>You render the post's HTML data with <code>dangerouslySetInnerHTML</code> . But you need to write lots of CSS to handle the dynamic content coming from the Ghost CMS API. </p>
<p>To solve that, I used the <code>@tailwindcss/typography</code> package. I also downloaded <code>cards.min.css</code> from Ghost. Now you don't need to write a single line of CSS in your Next app.</p>
<p>Generate the static site with the <code>generateStaticParams</code> function. Before, we used to <code>getStaticProps</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-comment">// fetch All posts</span>

  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> getPosts()

  <span class="hljs-comment">// return the slug </span>

  <span class="hljs-keyword">return</span> posts.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> ({
    slug: post.slug,
  }));

}
</code></pre>
<h3 id="heading-ia"> </h3>
<p>How to Build the Tag Page</p>
<p>I designed a simple tag page for the blog. The tag page shows articles related to the tags that are used. </p>
<p>You can also create a category page. Tag pages and category pages use the same logic and functionalities.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghostandnextjs-tag.png" alt="Tag page" width="600" height="400" loading="lazy">
<em>Tag page</em></p>
<p>Similar to the reading page, we'll get articles based on tags from the Ghost CMS API.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-comment">// return all posts realted to tag slug</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTagPosts</span>(<span class="hljs-params">tagSlug: <span class="hljs-built_in">string</span></span>) </span>{

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts.browse({ filter: <span class="hljs-string">`tag:<span class="hljs-subst">${tagSlug}</span>`</span>, include: <span class="hljs-string">'count.posts'</span> })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
    });
  ;

}

<span class="hljs-comment">// return all the slugs to build static with generateStaticParams</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllTags</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.tags.browse({
    limit: <span class="hljs-string">"all"</span>
  }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(err)
  })
}
</code></pre>
<p>The <code>getTagPosts(&lt;tag-slug&gt;)</code> function returns all the available posts related to a specific tag. </p>
<p>After receiving all posts with <code>getTagPosts()</code>, we render all posts with the help of the <code>map()</code> method.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/tag/[slug]/page.tsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">"../../Card"</span>

<span class="hljs-keyword">import</span> { getTagPosts, getAllTags } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../ghost-client"</span>

<span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostsOrPages } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> allTags: Tags = <span class="hljs-keyword">await</span> getAllTags()

  <span class="hljs-keyword">let</span> allTagsItem: { slug: <span class="hljs-built_in">string</span> }[] = []

<span class="hljs-comment">// genrate the slug for static site</span>

  allTags?.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
    allTagsItem.push({
      slug: item.slug,
    })
  })

  <span class="hljs-keyword">return</span> allTagsItem

}


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Tag</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>) </span>{

  <span class="hljs-keyword">let</span> tagPosts: PostsOrPages = <span class="hljs-keyword">await</span> getTagPosts(params.slug)

<span class="hljs-comment">// Handling 404 error</span>

  <span class="hljs-keyword">if</span> (tagPosts.length === <span class="hljs-number">0</span>) {
    notFound()
  }

  <span class="hljs-keyword">return</span> (
    &lt;aside aria-label=<span class="hljs-string">"Related articles"</span> className=<span class="hljs-string">"py-8 lg:py-24 dark:bg-gray-800"</span>&gt;

      &lt;div className=<span class="hljs-string">"px-4 mx-auto max-w-screen-xl"</span>&gt;

        &lt;h2 className=<span class="hljs-string">"mb-8 text-2xl font-bold text-gray-900 dark:text-white"</span>&gt;
          More articles <span class="hljs-keyword">from</span> {params.slug.split(<span class="hljs-string">"-"</span>).join(<span class="hljs-string">" "</span>)}
        &lt;/h2&gt;

        &lt;div className=<span class="hljs-string">"container my-12 mx-auto grid grid-cols-1 gap-12 md:gap-12 lg:gap-12  lg:grid-cols-3  md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4 "</span>&gt;

          {
            tagPosts.map(
              <span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> &lt;Card key={item.uuid} item={item} /&gt;
            )
          }

        &lt;/div&gt;

      &lt;/div&gt;

    &lt;/aside&gt;
  )

}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Tag
</code></pre>
<p>Generate the static site with the <code>generateStaticParams</code> function. It helps to generate slugs of the static build.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllTags</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.tags.browse({
    limit: <span class="hljs-string">"all"</span>
  }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(err)
  })
}
</code></pre>
<h3 id="heading-how-to-build-the-author-page">How to Build the Author Page</h3>
<p>The last and one of the most important pages for the blog site is the author page. This is where readers can learn more about the author. </p>
<p>For the demo blog, I designed a basic page for the author. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/nextandghostauthor.png" alt="Image" width="600" height="400" loading="lazy">
<em>Author page</em></p>
<p>We'll build this in a similar way as we built the tag page. First, we get the author's metadata and author posts from the Ghost CMS API. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-comment">// get author meta Data</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSingleAuthor</span>(<span class="hljs-params">authorSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.authors
    .read({
      slug: authorSlug
    }, { include: [<span class="hljs-string">"count.posts"</span>] })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err)
    });

}

<span class="hljs-comment">// get author related posts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSingleAuthorPosts</span>(<span class="hljs-params">authorSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts.browse({ filter: <span class="hljs-string">`authors:<span class="hljs-subst">${authorSlug}</span>`</span> })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err)
    })
};

<span class="hljs-comment">// get All author from Ghost CMS for generateStaticParams</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllAuthors</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.authors
    .browse({
      limit: <span class="hljs-string">"all"</span>
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
    });

}
</code></pre>
<p>The <code>getSingleAuthor(&lt;author-slug&gt;)</code> returns data about a single author based on the author slug, and the  <code>getSingleAuthorPosts(&lt;author-slug&gt;)</code> function returns all posts related to the author.</p>
<p>We render the posts data with the help of the <code>map()</code> method. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/author/[slug]/page.tsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { FaFacebook, FaTwitter, FaGlobe } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">"../../Card"</span>

<span class="hljs-keyword">import</span> { getSingleAuthor, getSingleAuthorPost, getAllAuthors } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../ghost-client"</span>

<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>;
<span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Author, PostsOrPages } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;



<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> allAuthor: Author[] = <span class="hljs-keyword">await</span> getAllAuthors()


  <span class="hljs-keyword">let</span> allAuthorItem: { slug: <span class="hljs-built_in">string</span> }[] = []

  allAuthor.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
    allAuthorItem.push({
      slug: item.slug,
    })
  })
  <span class="hljs-keyword">return</span> allAuthorItem

}


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AuthorPage</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>) </span>{

  <span class="hljs-keyword">const</span> getAuthor: Author = <span class="hljs-keyword">await</span> getSingleAuthor(params.slug)

  <span class="hljs-keyword">const</span> allAuthor: PostsOrPages = <span class="hljs-keyword">await</span> getSingleAuthorPost(params.slug)

<span class="hljs-comment">// Handling 404 errors</span>
  <span class="hljs-keyword">if</span> (allAuthor?.length === <span class="hljs-number">0</span>) {
    notFound()
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;section className=<span class="hljs-string">"dark:bg-gray-900"</span>&gt;
        &lt;div className=<span class="hljs-string">"py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6"</span>&gt;

          &lt;div className=<span class="hljs-string">" p-10 text-gray-500 sm:text-lg dark:text-gray-400"</span>&gt;

            {
              getAuthor?.profile_image !== <span class="hljs-literal">undefined</span> ? &lt;Image height={<span class="hljs-number">30</span>} width={<span class="hljs-number">30</span>} className=<span class="hljs-string">"w-36 h-36 p-2 rounded-full mx-auto ring-2 ring-gray-300 dark:ring-gray-500"</span> src={getAuthor?.profile_image} alt={getAuthor?.name} /&gt; : <span class="hljs-string">""</span>
            }

            {
              getAuthor?.name ? &lt;h2 className=<span class="hljs-string">"mb-4 mt-4 text-4xl tracking-tight font-bold text-center text-gray-900 dark:text-white"</span>&gt;
                {getAuthor?.name.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">0</span>]}
                &lt;span className=<span class="hljs-string">"font-extrabold"</span>&gt;
                  {getAuthor?.name?.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>]}
                &lt;/span&gt;
              &lt;/h2&gt; : <span class="hljs-string">""</span>
            }

            &lt;p className=<span class="hljs-string">"mb-4 font-light text-center"</span>&gt;{getAuthor?.bio} &lt;/p&gt;


            &lt;ul className=<span class="hljs-string">"flex flex-wrap p-4 justify-center md:space-x-8 md:mt-0 md:text-sm md:font-medium"</span>&gt;

              {
                (getAuthor?.website !== <span class="hljs-literal">null</span>) ? (&lt;li&gt;
                  &lt;Link href={getAuthor?.website} className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 hover:text-blue-700 dark:hover:text-blue-700 rounded md:p-0 dark:text-white"</span> aria-current=<span class="hljs-string">"page"</span>&gt;
                    &lt;FaGlobe /&gt;
                  &lt;<span class="hljs-regexp">/Link&gt; &lt;/</span>li&gt;) : <span class="hljs-string">" "</span>


              }

              {
                (getAuthor?.twitter !== <span class="hljs-literal">null</span>) ? (&lt;li&gt;
                  &lt;Link href={getAuthor?.twitter} className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 rounded hover:text-blue-700 dark:hover:text-blue-700 md:p-0 dark:text-white"</span> aria-current=<span class="hljs-string">"page"</span>&gt;
                    &lt;FaTwitter /&gt;
                  &lt;/Link&gt;
                &lt;/li&gt;) : <span class="hljs-string">" "</span>
              }

              {
                (getAuthor?.facebook !== <span class="hljs-literal">null</span> &amp;&amp; getAuthor.facebook !== <span class="hljs-literal">undefined</span>) ? (&lt;li&gt;
                  &lt;Link href={getAuthor?.facebook}
                    className=<span class="hljs-string">"block py-2 pl-3 pr-4 text-gray-700 rounded  hover:text-blue-700 dark:hover:text-blue-700 md:p-0 dark:text-white"</span>&gt; &lt;FaFacebook /&gt;
                  &lt;/Link&gt;
                &lt;/li&gt;) : <span class="hljs-string">" "</span>

              }

            &lt;/ul&gt;

          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/section&gt;

      &lt;aside aria-label=<span class="hljs-string">"Related articles"</span> className=<span class="hljs-string">"py-8 lg:py-24 dark:bg-gray-800"</span>&gt;
        &lt;div className=<span class="hljs-string">"px-4 mx-auto max-w-screen-xl"</span>&gt;

          &lt;h2 className=<span class="hljs-string">"mb-8 text-2xl font-bold text-gray-900 dark:text-white"</span>&gt;
            More articles <span class="hljs-keyword">from</span> {getAuthor?.name}
          &lt;/h2&gt;

          &lt;div className=<span class="hljs-string">"container my-12 mx-auto grid grid-cols-1 gap-12 md:gap-12 lg:gap-12  lg:grid-cols-3  md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4 "</span>&gt;

            {
              allAuthor?.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> &lt;Card key={item?.uuid} item={item} /&gt;)
            }

          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/aside&gt;


    &lt;/&gt;
  )

}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AuthorPage
</code></pre>
<p>To generate the author slug for the static site, we need to use the <code>generateStaticParams</code> function. We do not need anything else to build the static site.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-comment">// Build Static Site </span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> allAuthor: Author[] = <span class="hljs-keyword">await</span> getAllAuthors()


  <span class="hljs-keyword">let</span> allAuthorItem: { slug: <span class="hljs-built_in">string</span> }[] = []

  allAuthor.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
    allAuthorItem.push({
      slug: item.slug,
    })
  })
  <span class="hljs-keyword">return</span> allAuthorItem

}
</code></pre>
<h3 id="heading-how-to-build-single-pages">How to Build Single Pages</h3>
<p>For single pages like About, Contact, Privacy Policy, and so on, you can also create them with the Ghost Content API.</p>
<p>Our single-page design looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/single-blog.png" alt="single blog page" width="600" height="400" loading="lazy">
<em>single blog page</em></p>
<p>Firstly, you need to fetch all pages and the single pages data from the Ghost Content API. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.tsx</span>

<span class="hljs-comment">// fetch all pages</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSinglePage</span>(<span class="hljs-params">pageSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.pages
    .read({
      slug: pageSlug
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(err);
    });
}

<span class="hljs-comment">// single page data </span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSinglePage</span>(<span class="hljs-params">pageSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.pages
    .read({
      slug: pageSlug
    }, { include: [<span class="hljs-string">"tags"</span>] })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(err);
    });
}
</code></pre>
<p>The <code>getSinglePage(page-slug)</code> function returns the single page data based on the page slug, and the <code>getAllPages()</code> function returns all the available published page data to generate the dynamic params with the <code>generateStaticParams()</code> function. </p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/pages/[slug]/page.tsx</span>

<span class="hljs-keyword">import</span> { getSinglePage, getAllPages } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../ghost-client"</span>
<span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostOrPage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../../cards.min.css"</span>

<span class="hljs-comment">// genrate Static slug or params for blog</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> pages = <span class="hljs-keyword">await</span> getAllPages()

  <span class="hljs-keyword">return</span> pages.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> ({
    slug: post.slug,
  }));
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Pages</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>) </span>{

<span class="hljs-comment">// fetch single page</span>
  <span class="hljs-keyword">const</span> getPage = <span class="hljs-keyword">await</span> getSinglePage(params.slug)

  <span class="hljs-comment">// handle 404 error</span>
  <span class="hljs-keyword">if</span> (!getPage) {
    notFound()
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;main className=<span class="hljs-string">"pt-8 pb-16 lg:pt-16 lg:pb-24 dark:bg-gray-900"</span>&gt;
        &lt;div className=<span class="hljs-string">"flex justify-between px-4 mx-auto max-w-screen-xl "</span>&gt;

          &lt;article className=<span class="hljs-string">"mx-auto w-full max-w-3xl prose prose-xl prose-p:text-gray-800  dark:prose-p:text-gray-100 sm:prose-base prose-a:no-underline prose-blue dark:prose-invert"</span>&gt;


            &lt;h1 className=<span class="hljs-string">"mb-14 text-3xl font-extrabold leading-tight text-gray-900 lg:mb-6 lg:text-4xl dark:text-white"</span>&gt;
              {getPage.title}
            &lt;/h1&gt;

            &lt;div dangerouslySetInnerHTML={{ __html: getPage?.html }}&gt;&lt;/div&gt;

          &lt;/article&gt;
        &lt;/div&gt;
      &lt;/main&gt;
    &lt;/&gt;
  )

}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Pages
</code></pre>
<h3 id="heading-how-to-handle-pagination">How to Handle Pagination</h3>
<p>Pagination helps speed up your site as well as divide your site into smaller parts, more digestible pages. You can link your posts with each other with <code>prev</code> and <code>next</code>. </p>
<pre><code class="lang-json">meta:{
    pagination: { page: <span class="hljs-number">1</span>, limit: <span class="hljs-number">10</span>, pages: <span class="hljs-number">2</span>, total: <span class="hljs-number">12</span>, next: <span class="hljs-number">2</span>, prev: <span class="hljs-literal">null</span> }
 }
</code></pre>
<p>Firstly, we'll create a <code>Pagination.tsx</code> file as a React component.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Pagination.tsx</span>

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>
<span class="hljs-keyword">import</span> { Pagination } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PaginationItem</span>(<span class="hljs-params">{ item }: { item: Pagination }</span>) </span>{

  <span class="hljs-keyword">let</span> paginationItems = []

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> index = <span class="hljs-number">1</span>; index &lt;= item?.pages; index++) {
    paginationItems.push(&lt;li key={index * <span class="hljs-number">2</span>} &gt;&lt;Link href={index === <span class="hljs-number">1</span> ? <span class="hljs-string">"/"</span> : <span class="hljs-string">`/pagination/<span class="hljs-subst">${index}</span>`</span>} className=<span class="hljs-string">"px-3 py-2 leading-tight bg-blue-100 hover:bg-blue-200 border-transparent border rounded-lg text-black dark:bg-gray-800 dark:text-gray-400 mx-2 dark:hover:bg-gray-700 dark:hover:text-white"</span>&gt;
      {index}
    &lt;<span class="hljs-regexp">/Link&gt;&lt;/</span>li&gt;)
  }

  <span class="hljs-keyword">return</span> (

    &lt;nav aria-label=<span class="hljs-string">"pagination"</span> className=<span class="hljs-string">"mx-auto my-20 container"</span>&gt;

      &lt;ul className=<span class="hljs-string">"mx-auto flex justify-center -space-x-px"</span>&gt;

        &lt;li&gt;
          {
            item.prev ? &lt;Link href={item.prev === <span class="hljs-number">1</span> ? <span class="hljs-string">"/"</span> : <span class="hljs-string">`/pagination/<span class="hljs-subst">${item.prev}</span>`</span>} className=<span class="hljs-string">"px-3 py-2 mr-2 border border-transparent rounded-md  leading-tight bg-white hover:text-blue-700 dark:bg-gray-800 dark:text-gray-400
              dark:hover:bg-gray-700 dark:hover:text-white"</span>&gt;
              Prev
            &lt;/Link&gt; : <span class="hljs-string">" "</span>
          }
        &lt;/li&gt;

        {paginationItems}

        &lt;li&gt;
          {
            item.next ? &lt;Link href={<span class="hljs-string">`/pagination/<span class="hljs-subst">${item.next}</span>`</span>} className=<span class="hljs-string">"px-3 py-2 ml-2 border border-transparent rounded-md leading-tight bg-white hover:text-blue-700 dark:bg-gray-800 dark:text-gray-400
            dark:hover:bg-gray-700 dark:hover:text-white"</span>&gt;
              Next
            &lt;/Link&gt; : <span class="hljs-string">" "</span>
          }
        &lt;/li&gt;


      &lt;/ul&gt;

    &lt;/nav&gt;

  )

}


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> PaginationItem
</code></pre>
<p>When you call the <code>api.posts.browse({ limit: 10 })</code> request, the API endpoint returns ten posts and a <code>meta</code> object with <code>pagination</code>.</p>
<h4 id="heading-the-returned-apipostsbrowse-limit-10-data-look-like-this">The returned <code>api.posts.browse({ limit: 10 })</code> data look like this:</h4>
<pre><code class="lang-json"> [
  {title: 'Demo Posts with Nextjs and Ghost Editor',... },
  {title: Trigger the hook and rebuild the nextjs site',... }

meta:{
    pagination: { page: <span class="hljs-number">1</span>, limit: <span class="hljs-number">10</span>, pages: <span class="hljs-number">2</span>, total: <span class="hljs-number">12</span>, next: <span class="hljs-number">2</span>, prev: <span class="hljs-literal">null</span> }
  }
]
</code></pre>
<p>Now based on <code>meta</code>, we can create pagination and pass <code>meta.pagination</code> as props to the <code>Pagination</code> component.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/page.tsx</span>

<span class="hljs-keyword">import</span> { getPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ghost-client"</span>
<span class="hljs-keyword">import</span> Pagination <span class="hljs-keyword">from</span> <span class="hljs-string">"./Pagination"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> getPost = <span class="hljs-keyword">await</span> getPosts()

  <span class="hljs-keyword">const</span> AllPostForSerach = <span class="hljs-keyword">await</span> getSearchPosts()

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
     {<span class="hljs-comment">/* rest of code  */</span>}
      &lt;Pagination item={getPost.meta.pagination} /&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>To enable dynamic pagination, we'll create a <code>src/app/pagination/[item]/page.tsx</code> route in the blog. You can use whatever name you want for the pagination route.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.tsx</span>

<span class="hljs-comment">// return all posts for generateStaticParams</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPosts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts
    .browse({
      include: [<span class="hljs-string">"tags"</span>, <span class="hljs-string">"authors"</span>],
      limit: <span class="hljs-number">10</span>
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
    });
}

<span class="hljs-comment">// </span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPaginationPosts</span>(<span class="hljs-params">page: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts
    .browse({
      include: [<span class="hljs-string">"tags"</span>, <span class="hljs-string">"authors"</span>],
      limit: <span class="hljs-number">10</span>,
      page: page
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(err)
    });
}
</code></pre>
<p>The <code>getPosts</code> is used to render the <code>Pagination</code> component on the pagination page. The important part is the <code>getPaginationPosts(&lt;pagination-page-number&gt;)</code> function, which returns posts based on the pagination page number.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/pagination/[item]/page.tsx</span>

<span class="hljs-keyword">import</span> { getPaginationPosts, getPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../ghost-client"</span>
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">'../../Card'</span>
<span class="hljs-keyword">import</span> PaginationItem <span class="hljs-keyword">from</span> <span class="hljs-string">"../../Pagination"</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostsOrPages } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>;




<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStaticParams</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> posts:PostsOrPages = <span class="hljs-keyword">await</span> getPosts()

  <span class="hljs-keyword">let</span> paginationItem: { item: <span class="hljs-built_in">number</span> }[] = []

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> index = <span class="hljs-number">1</span>; index &lt;= posts?.meta.pagination.pages; index++) {
    paginationItem.push({
      item: index,
    })

  }

  <span class="hljs-keyword">return</span> paginationItem

}



<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Pagination</span>(<span class="hljs-params">{ params }: { params: { item: <span class="hljs-built_in">string</span> }; }</span>) </span>{

  <span class="hljs-keyword">let</span> getParams: <span class="hljs-built_in">number</span> = <span class="hljs-built_in">Number</span>.parseInt(params.item)

  <span class="hljs-keyword">const</span> getPost: PostsOrPages = <span class="hljs-keyword">await</span> getPaginationPosts(getParams)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;

      &lt;main className=<span class="hljs-string">"container my-12 mx-auto grid grid-cols-1 gap-2 md:gap-3 lg:gap-4 lg:grid-cols-3 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4"</span>&gt;

        {
          getPost?.map(
            <span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
              <span class="hljs-keyword">return</span> &lt;Card key={item.uuid} item={item} /&gt;
            })
        }
      &lt;/main&gt;

      &lt;PaginationItem item={getPost.meta.pagination} /&gt;

    &lt;/&gt;
  )
}
</code></pre>
<h3 id="heading-nextjs-seo">Next.js SEO</h3>
<p>If you are a blogger, you know how important SEO is in helping people find your blog and your articles. For SEO, Next.js provides a <code>generateMetadata</code> function to generate dynamic SEO metadata for your site. This means that you don't need any additional packages for SEO.  </p>
<p>For the purpose of this example, I'll explain how to enable SEO for the blog only on the Homepage and the Reading page. You can use the same logic to enable it on any of your other pages. </p>
<p>First, let's see how to enable SEO on the Homepage:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-comment">// Get you settings meta data from Ghost CMS</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getNavigation</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.settings.browse()
}
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/page.tsx</span>

<span class="hljs-keyword">import</span> { getNavigation } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ghost-client"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateMetadata</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Metadata</span>&gt; </span>{
  <span class="hljs-keyword">const</span> Metadata = <span class="hljs-keyword">await</span> getNavigation()
  <span class="hljs-keyword">return</span> {
    title: Metadata.title,
    description: Metadata.description,
    keywords: [<span class="hljs-string">'Next.js'</span>, <span class="hljs-string">'React'</span>, <span class="hljs-string">'JavaScript'</span>],
  }
}
</code></pre>
<p>Now we'll see how to enable SEO on the Reading page:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSinglePost</span>(<span class="hljs-params">postSlug: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts
    .read({
      slug: postSlug
    }, { include: [<span class="hljs-string">"tags"</span>, <span class="hljs-string">"authors"</span>] })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(err);
    });
}
</code></pre>
<p>The <code>generateMetadata</code> have params props, which help access the slug. Then, based on the slug, we get the data and return it. </p>
<pre><code class="lang-typescript">

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateMetadata</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Metadata</span>&gt; </span>{

  <span class="hljs-keyword">const</span> metaData: PostOrPage = <span class="hljs-keyword">await</span> getSinglePost(params.slug)

  <span class="hljs-keyword">let</span> tags = metaData?.tags.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item.name)

  <span class="hljs-keyword">return</span> {
    title: metaData.title,
    description: metaData.description,
    keywords: tags,
    openGraph: {
      title: metaData.title,
      description: metaData.excpet,
      url: metaData.url,
      keywords: tags,
      images: [
        {
          url: metaData.feature_image,
        },
      ],
      locale: metaData.locale,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'website'</span>,
    },
  }
}
</code></pre>
<h3 id="heading-how-to-enable-search">How to Enable Search</h3>
<p>Enabling search on a static blog is hard to do from scratch. Instead, you can use a third-party Node page like <a target="_blank" href="https://github.com/oramasearch/orama">Orama</a> or <a target="_blank" href="https://github.com/nextapps-de/flexsearch">Flex search</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/searchbarinnextjs.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>For our demo, we created a very simple search bar functionality without installing any additional packages. </p>
<p>Firstly, we get all posts from the Ghost CMS API.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ghost-client.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSearchPosts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> api.posts.browse({ limit: <span class="hljs-string">"all"</span>}).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(err)
  });
</code></pre>
<p>After we convert it into a string with the help of <code>JSON.stringify()</code>, we then create a new <code>search.json</code> file. On every request, it updates or rewrites our <code>search.json</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/app/page.tsx</span>

<span class="hljs-keyword">import</span> {  getSearchPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ghost-client"</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'node:fs'</span>;



<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{


<span class="hljs-comment">// get All posts for search </span>
  <span class="hljs-keyword">const</span> AllPostForSerach = <span class="hljs-keyword">await</span> getSearchPosts()

  <span class="hljs-comment">// Enable getSearch  </span>

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> jsonString = <span class="hljs-built_in">JSON</span>.stringify(AllPostForSerach)

    fs.writeFile(<span class="hljs-string">'search.json'</span>, jsonString, <span class="hljs-string">'utf8'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (err) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Error writing file'</span>, err)
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Successfully wrote file'</span>)
      }
    })

  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'error : '</span>, error)
  }


  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;main className=<span class="hljs-string">"container my-12 mx-auto grid grid-cols-1 gap-2 md:gap-3 lg:gap-4 lg:grid-cols-3 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4"</span>&gt;
      {<span class="hljs-comment">/* rest code... */</span>}
      &lt;/main&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>When you enter the text in the search input, based on the text query, we compare the query or text in the <code>serach.json</code> file data. If it matches the article title with the query, then we store the <code>searchPost</code> variable, and finally we render the stored data in the <code>searchPost</code> variable page.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>

<span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Popover <span class="hljs-keyword">from</span> <span class="hljs-string">'@radix-ui/react-popover'</span>;
<span class="hljs-keyword">import</span> { FaSearch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">'next/link'</span>;
<span class="hljs-keyword">import</span> searchData <span class="hljs-keyword">from</span> <span class="hljs-string">'../../search.json'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PostOrPage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tryghost/content-api"</span>


<span class="hljs-keyword">let</span> searchPost: PostOrPage[] = []


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Search</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> [query, setQuery] = useState(<span class="hljs-literal">null</span>)

  useEffect(<span class="hljs-function">() =&gt;</span> {

    searchPost.length = <span class="hljs-number">0</span>;

    searchData.map(<span class="hljs-function">(<span class="hljs-params">item: PostOrPage</span>) =&gt;</span> {

      <span class="hljs-keyword">if</span> (item?.title.trim().toLowerCase().includes(query?.trim().toLowerCase())) {
        searchPost.push(item)
      }

    })

  }, [query])


  <span class="hljs-keyword">return</span> (
    &lt;Popover.Root&gt;
      &lt;Popover.Trigger asChild&gt;
        &lt;button
          className=<span class="hljs-string">"cursor-pointer outline-none"</span>
          aria-label=<span class="hljs-string">"Search"</span>
        &gt;
          &lt;FaSearch /&gt;
        &lt;/button&gt;
      &lt;/Popover.Trigger&gt;

      &lt;Popover.Portal&gt;

        &lt;Popover.Content
          className=<span class="hljs-string">"rounded p-2 bg-white dark:bg-gray-800 w-[480px] will-change-[transform,opacity] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade"</span>
          sideOffset={<span class="hljs-number">5</span>}
        &gt;

          &lt;div className=<span class="hljs-string">'my-2'</span>&gt;
            &lt;label htmlFor=<span class="hljs-string">"default-search"</span> className=<span class="hljs-string">"mb-2 mt-5 text-sm font-medium text-gray-900 sr-only dark:text-white"</span>&gt;Search bar &lt;/label&gt;
            &lt;div className=<span class="hljs-string">"relative"</span>&gt;
              &lt;div className=<span class="hljs-string">"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"</span>&gt;
                &lt;svg className=<span class="hljs-string">"w-5 h-5 text-gray-500 dark:text-gray-400"</span> fill=<span class="hljs-string">"none"</span> stroke=<span class="hljs-string">"currentColor"</span> viewBox=<span class="hljs-string">"0 0 24 24"</span> xmlns=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>&gt;&lt;path strokeLinecap=<span class="hljs-string">"round"</span> strokeLinejoin=<span class="hljs-string">"round"</span> strokeWidth=<span class="hljs-string">"2"</span> d=<span class="hljs-string">"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"</span>&gt;&lt;<span class="hljs-regexp">/path&gt;&lt;/</span>svg&gt;
              &lt;/div&gt;
              &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"search"</span> id=<span class="hljs-string">"default-search"</span> onChange={<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> setQuery(event?.target.value)} className=<span class="hljs-string">"block w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"</span> placeholder=<span class="hljs-string">"Start searching here ..."</span> required /&gt;

            &lt;/div&gt;
          &lt;/div&gt;


          {

            serachPost.length &gt; <span class="hljs-number">0</span> ? serachPost.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {

              <span class="hljs-keyword">return</span> (
                &lt;div key={item.uuid} className=<span class="hljs-string">'my-3'</span>&gt;
                  &lt;div className=<span class="hljs-string">"text-white my-2 py-2 bg-blue-400 dark:bg-gray-900 dark:hover:bg-blue-400 border-none rounded-md dark:text-white"</span>&gt;
                    &lt;Link href={<span class="hljs-string">`read/<span class="hljs-subst">${item.slug}</span>`</span>} className=<span class="hljs-string">"relative inline-flex items-center rounded-lg w-full px-4 py-2 text-sm font-medium"</span>&gt;
                      {item.title}
                    &lt;/Link&gt;
                  &lt;/div&gt;
                &lt;/div&gt;
              )
            }) : <span class="hljs-string">" "</span>

          }

        &lt;/Popover.Content&gt;

      &lt;/Popover.Portal&gt;

    &lt;/Popover.Root &gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Search;
</code></pre>
<h3 id="heading-error-handling">Error Handling</h3>
<p>Next.js has two types of <a target="_blank" href="https://beta.nextjs.org/docs/routing/error-handling#how-errorjs-works">error handling</a>. the first is layout-based, and the second is <a target="_blank" href="https://beta.nextjs.org/docs/routing/error-handling#handling-errors-in-root-layouts">global error</a> handling. For the demo here, we'll use layout-based error handling.</p>
<p>Next provides a special type of <code>error.tsx</code> file to handle errors on your site. It does not handle 404, 500, and so on – it handles only runtime errors.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>; <span class="hljs-comment">// Error components must be Client components</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">'next/link'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Error</span>(<span class="hljs-params">{ error, reset }: { error: <span class="hljs-built_in">Error</span>; reset: () =&gt; <span class="hljs-built_in">void</span>; }</span>) </span>{

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(error);
  }, [error]);

  <span class="hljs-keyword">return</span> (
    &lt;section className=<span class="hljs-string">"dark:bg-gray-900 my-16"</span>&gt;

      &lt;div className=<span class="hljs-string">"py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6"</span>&gt;

        &lt;div className=<span class="hljs-string">"mx-auto max-w-screen-sm text-center"</span>&gt;

          &lt;h1 className=<span class="hljs-string">"mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500"</span>&gt;Something wrong&lt;/h1&gt;
          &lt;p className=<span class="hljs-string">"mb-4 text-lg p-2 font-light bg-red-500 text-white dark:bg-red-400 dark:text-white"</span>&gt;{error.message}&lt;/p&gt;

          &lt;div className=<span class="hljs-string">'flex justify-around mt-2'</span>&gt;

            &lt;Link href=<span class="hljs-string">"#"</span> className=<span class="hljs-string">"inline-flex bg-gray-600 text-white hover:bg-gray-700 focus:ring-4 font-medium rounded-lg text-sm p-2
                text-center"</span>&gt;Back to Homepage&lt;/Link&gt;

            &lt;button className=<span class="hljs-string">'bg-gray-600 text-white rounded-lg p-2'</span> onClick={<span class="hljs-function">() =&gt;</span> reset()}&gt;
              Try again
            &lt;/button&gt;


          &lt;/div&gt;

        &lt;/div&gt;

      &lt;/div&gt;

    &lt;/section&gt;
  );
}
</code></pre>
<h4 id="heading-how-to-handle-404-errors">How to handle 404 errors</h4>
<p>To handle 404 errors in the Next.js app folder, you need to create a <code>not-found.tsx</code> file in your root level. </p>
<p>Our 404 file looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/nextjsandghosterror.png" alt="404 error" width="600" height="400" loading="lazy">
<em>404 error</em></p>
<p>Here's the code for that:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NotFound</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">return</span> (
    &lt;section className=<span class="hljs-string">"bg-white dark:bg-gray-900 my-16"</span>&gt;
      &lt;div className=<span class="hljs-string">"py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6"</span>&gt;
        &lt;div className=<span class="hljs-string">"mx-auto max-w-screen-sm text-center"</span>&gt;
          &lt;h1 className=<span class="hljs-string">"mb-4 text-7xl tracking-tight lg:text-9xl text-primary-600 dark:text-primary-500"</span>&gt;<span class="hljs-number">404</span>&lt;/h1&gt;
          &lt;p className=<span class="hljs-string">"mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white"</span>&gt; Something wrong&lt;/p&gt;
          &lt;p className=<span class="hljs-string">"mb-4 text-lg font-light text-gray-500 dark:text-gray-400"</span>&gt;
            Sorry, we cant find that article. You will find lots to explore on the home page.
          &lt;/p&gt;
          &lt;Link href=<span class="hljs-string">"/"</span> className=<span class="hljs-string">"inline-flex text-white bg-black dark:bg-white dark:text-black p-3 hover:bg-gray-800 my-4"</span>&gt;Back to Homepage&lt;/Link&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section &gt;
  )

}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NotFound
</code></pre>
<p>The big issue with the <code>not-found.tsx</code> error file is that it doesn't show automatically in Next (v13.3.0). To show a 404 error, you need to show the error manually. Here's how you do that:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { notFound } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Read</span>(<span class="hljs-params">{ params }: { params: { slug: <span class="hljs-built_in">string</span> }; }</span>) </span>{

  <span class="hljs-keyword">const</span> getPost = <span class="hljs-keyword">await</span> getSinglePost(params.slug)

  <span class="hljs-comment">// if not found getPost, then show 404 error</span>

  <span class="hljs-keyword">if</span> (!getPost) {
    notFound()
  }

  <span class="hljs-keyword">return</span> (
      &lt;main className=<span class="hljs-string">"pt-8 pb-16 lg:pt-16 lg:pb-24 dark:bg-gray-900"</span>&gt;

          rest <span class="hljs-keyword">of</span> code ....

      &lt;/main&gt;
      )
}
</code></pre>
<h3 id="heading-how-to-rebuild-your-static-site-with-webhooks">How to Rebuild Your Static Site with Webhooks</h3>
<p>The biggest problem when you create a static site happens if somebody writes a new post or changes an existing post in Ghost. For a personal project, you can manually redeploy your site. But for a larger site, you won't be able to do that every time this happens.</p>
<p>The best solution is to use webhooks. Ghost provides webhook support. If you update an existing post or write a new one, it'll update in Ghost. </p>
<p>In the demo project, we're using Vercel webhooks to deploy our blog. When we create a new blog or update something on the site, Ghost triggers the Vercel webhook. Then Vercel rebuilds the site as needed.</p>
<p>You do not need to write the code for this – just follow along and copy-paste as you go.</p>
<h4 id="heading-how-to-get-the-webhook-from-vercel">How to get the webhook from Vercel</h4>
<p>Firstly, go to the Vercel dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/select1.png" alt="Vercel dashboard" width="600" height="400" loading="lazy">
<em>Vercel dashboard</em></p>
<p>Select your project, where you'll deploy your Ghost frontend.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/select2.png" alt="Select the project in your vercel dashboard" width="600" height="400" loading="lazy">
<em>Select the project in your Vercel dashboard</em></p>
<p>Click on the settings tab in your Vercel project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/select3.png" alt="Click the Git tab" width="600" height="400" loading="lazy">
<em>Click the Git tab</em></p>
<p>Then click on the Git tab. After scrolling down, you can see the deploy hook selection.  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/select4.png" alt="Go to Deploy hooks sections" width="600" height="400" loading="lazy">
<em>Go to Deploy hooks sections</em></p>
<p>Enter your webhook name and branch name and click on the "create hook" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/select5.png" alt="Copy your webhook url" width="600" height="400" loading="lazy">
<em>Copy your webhook url</em></p>
<p>Click on the copy button to copy your vercel webhook. </p>
<h4 id="heading-how-to-integrate-vercel-webhooks-in-the-ghost-dashboard">How to integrate Vercel webhooks in the Ghost dashboard</h4>
<p>When something changes in Ghost, it triggers the Vercel webhook URL. Then Vercel redeploys the blog site. </p>
<p>To integrate the Vercel webhook with Ghost, just follow these steps:</p>
<p>Open the Ghost CMS dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghost1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Ghost dashboard</em></p>
<p>Click on the setting icon.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghost3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Ghost settings</em></p>
<p>Click on the New custom integration button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghost4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add a new custom integration</em></p>
<p>Enter the integration name.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghost5.png" alt="Image" width="600" height="400" loading="lazy">
<em>Add integration name</em></p>
<p>Click to add the webhook button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/ghost7.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to add the webhook</em></p>
<p>First, enter the name, then select Event and paste the URL which you copied from the Vercel dashboard.</p>
<p>Based on the event, Ghost will call the webhook, and your website will rebuild. Redeploys take time based on how big your site is, and so on.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Everything should work well using Next.js and the Ghost CMS as we've worked through in this tutorial.</p>
<p>But some of the Ghost editor components, like toggles, where you need JavaScript interaction, don't work. You can solve this by writing your own JavaScript or getting a JavaScript file for Ghost and adding it to the <code>read/[slug]/page.tsx</code> file.  </p>
<p>You can save a lot of money on hosting by combining Next.js and the Ghost CMS API, but you lose some features like inbuilt signup, login, accounts, subscriptions, search bar, and member access levels.</p>
<p>You can share and follow me on <a target="_blank" href="https://twitter.com/Official_R_deep">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/officalrajdeepsingh/">Linkedin</a>. If you like my work, you can read more content on my blog, the <a target="_blank" href="https://officialrajdeepsingh.dev/">officialrajdeepsingh.dev</a>, <a target="_blank" href="https://medium.com/frontendweb">frontend web</a>, and Sign up for my <a target="_blank" href="https://officialrajdeepsingh.medium.com/subscribe">free newsletter</a>.</p>
<p>You can also check out <a target="_blank" href="https://github.com/officialrajdeepsingh/awesome-nextjs">awesome-next</a>, a curated list of awesome Nextjs-based libraries that help build small and large-scale applications with Next.js.</p>
<p>Here are some additional resources you can use if you need more help or information while going through this tutorial:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://ghost.org/docs/jamstack/next/">https://ghost.org/docs/jamstack/next/</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.digitalocean.com/community/tutorials/how-to-build-your-blog-on-digitalocean-with-ghost-and-next-js">https://www.digitalocean.com/community/tutorials/how-to-build-your-blog-on-digitalocean-with-ghost-and-next-js</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://ghost.org/docs/content-api/">https://ghost.org/docs/content-api/</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://beta.nextjs.org/docs/getting-started">https://beta.nextjs.org/docs/getting-started</a></div>
<p>I write tons of articles on Next. If you are interested in Next and related stuff, you can follow me on <a target="_blank" href="https://officialrajdeepsingh.medium.com/">Medium</a> and join the <a target="_blank" href="https://medium.com/frontendweb">frontend web publication</a>.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Table of Contents Component for Your Blog ]]>
                </title>
                <description>
                    <![CDATA[ By Caleb Olojo When you visit documentation sites, you'll notice that many of them have a common component: the <TableOfContent /> component. The idea behind it is to give the reader a "heads-up" about the information they're trying to consume. This ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-table-of-contents-component/</link>
                <guid isPermaLink="false">66d45dd936c45a88f96b7cbf</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 03 Mar 2023 14:54:50 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/toc.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Caleb Olojo</p>
<p>When you visit documentation sites, you'll notice that many of them have a common component: the <code>&lt;TableOfContent /&gt;</code> component.</p>
<p>The idea behind it is to give the reader a "heads-up" about the information they're trying to consume.</p>
<p>This feature, in turn, helps the reader go directly to the section that includes a solution to whatever bug or issue they're facing, without reading the whole article. It contributes to a good User Experience because you end up saving your audience the hassle of extra scrolling and searching.</p>
<p>I have a personal <a target="_blank" href="https://meje.dev/blog">blog</a> that I dedicate a lot of my time to. And for a long time, I thought about adding this feature. It will help anyone visiting my site to enjoy their time and find what they need.</p>
<p>This article is a summary of my process, so you don't have to go through the issues I went through. If you're trying to add a Table of Contents feature to your blog, you can walk through it with me.</p>
<p>I shared a video of what the component looked like after completing it. You can take a look at it <a target="_blank" href="https://twitter.com/calebolojo/status/1629113931066142720">here</a>.</p>
<h2 id="heading-how-to-get-heading-text-from-the-frontmatter"><strong>How to Get Heading Text from the Frontmatter</strong></h2>
<p>To build a table of contents feature, I knew what I needed to do. Since the articles on my blog are written in markdown, I am just using a superset of markdown – MDX – which allows me to use React components in markdown files.</p>
<p>The first thing on my list was to get a way to render the heading text in a component. This way, when people clicked on the headings, the browser would scroll to that point in the article.</p>
<p>With HTML, you can achieve this by using the anchor tag and passing the value to an <code>href</code> attribute.</p>
<p>To have linked text pointing to a section, the ideal way of doing this would look like what's in the snippet below:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#section-one"</span>&gt;</span>Go to section one<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#section-two"</span>&gt;</span>Go to section two<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#section-three"</span>&gt;</span>Go to section three<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"section-one"</span>&gt;</span>some content<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"section-two"</span>&gt;</span>yet, a content that seems weird<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"section-three"</span>&gt;</span>some content, again<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<p>In the snippet above, the anchor tags are tied to the sections with respect to their <code>id</code> attribute in the DOM. When you click on any text, it takes you to the respective section.</p>
<p>With this mental model, I thought of populating the frontmatter of each article with the headings in all the articles I've written. I knew it was going to be stressful, but I went with it anyway.</p>
<p>For context, this is what a frontmatter in a markdown file looks like. The frontmatter contains the metadata of all articles on my blog. Details like the title, date it was published, the tags or category that the article falls into, the description, a canonical URL, and any other thing you may feel like adding to improve your article's SEO.</p>
<p>This pattern is common when you're building blogs with Next.js and MDX (markdown in general). It has a YAML-like syntax too.</p>
<pre><code class="lang-bash">---
id: 20
title: Building a Table of Content component
publishedAt: <span class="hljs-string">'2023-02-28'</span>
excerpt: description of the article
tags:
  - ux
  - nextjs
headings:
  - heading-one
  - heading-two
  - heading-three
cover_image: /img/covers/toc.jpg
---
</code></pre>
<p>The snippet above is what the frontmatter of this article looks like, but with the <code>headings</code> entry. I'm going to use that to explain my initial approach. If I go ahead and map through the frontmatter, I'll be able to retrieve the content from the headings array.</p>
<p>It's great because I'll be able to use the items in the <code>headings</code> array in the TableOfContent component. It felt surreal, and I was elated for a minute. The component looked like this:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { HeadingContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'./style/toc.styled'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TableOfContents</span>(<span class="hljs-params">{ headings }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HeadingContainer</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>In this article<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
        {headings.map((item, index) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`#${<span class="hljs-attr">item</span>}`}&gt;</span>{item}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">HeadingContainer</span>&gt;</span></span>
  )
}
</code></pre>
<p>The component above receives a headings prop alone, which in turn receives a value from the frontmatter through the Next.js <code>getStaticProps()</code> method.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Blog</span>(<span class="hljs-params">{
  post: {
    frontmatter: { title, headings },
  },
}</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TableOfContents</span> <span class="hljs-attr">headings</span>=<span class="hljs-string">{headings}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  )
}

<span class="hljs-comment">// destructuring params to get the unique slugs</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{ params }</span>) </span>{
  <span class="hljs-keyword">const</span> { slug } = params
  <span class="hljs-keyword">const</span> { frontmatter } = <span class="hljs-keyword">await</span> getArticleFromSlug(slug)

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">post</span>: {
        frontmatter,
      },
    },
  }
}
</code></pre>
<p>If everything in the snippets above seems a bit confusing, you can take a look at this article where I wrote about the <a target="_blank" href="https://meje.dev/blog/how-i-built-this-blog">process of setting up a Next.js blog</a>.</p>
<p>With that out of the way, the component rendered the list of items from the frontmatter, and it looked fine.</p>
<p>But, the moment I clicked on an item, hoping to be scrolled to that section, it did not work as expected. I ran into an error, which you'll see in the next section.</p>
<h2 id="heading-how-to-use-extract-md-headings"><strong>How to Use extract-md-headings</strong></h2>
<p>I realized that when I clicked on an item in the component, the browser encoded the URL of the current slug with an encoding parameter for spaces – <code>%20%</code> – which in turn led to the issue.</p>
<p>Although, I realized that it also could be the way I was referencing the heading elements in the <code>frontmatter</code>. But that didn't end up mattering, because I found an alternative and it worked great.</p>
<p>After I made sure that it worked perfectly, I went on and published that alternative as a <a target="_blank" href="https://npmjs.com/package/extract-md-headings">package</a> to the npm registery.</p>
<p>The package extends a function, <code>extractHeadings()</code>, that accepts a string, as a path, to where the markdown file is and extracts any text that matches how heading texts are written in markdown files. You can take a look at the source code <a target="_blank" href="https://github.com/kaf-lamed-beyt/extract-md-headings/blob/834ad610c80db6a367260b3ec6927c9cd2099a5c/src/index.ts#L15-L36">here</a> if you want to see how it works under the hood.</p>
<p>With this tool in my arsenal, I modified the <code>getStaticProps</code> method to use the function. Why? you might ask me. Well, because the package depends solely on Node's <code>fs</code> module, which equates to a server-side scripting approach.</p>
<p>With Next.js we can perform server-side operations in the pages directory with any of the data-fetching methods, <code>getStaticProps</code>, <code>getStaticPaths</code>, and <code>getServerSideProps</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { extractHeadings } <span class="hljs-keyword">from</span> <span class="hljs-string">'extract-md-headings'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Blog</span>(<span class="hljs-params">{
  post: {
    fileContent,
    frontmatter: { title },
  },
}</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">TableOfContents</span> <span class="hljs-attr">headings</span>=<span class="hljs-string">{fileContent}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{ params }</span>) </span>{
  <span class="hljs-keyword">const</span> { slug } = params
  <span class="hljs-keyword">const</span> { frontmatter } = <span class="hljs-keyword">await</span> getArticleFromSlug(slug)
  <span class="hljs-keyword">const</span> mdxContent = extractHeadings(<span class="hljs-string">`/path/to/where/<span class="hljs-subst">${slug}</span>.mdx`</span>)

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">post</span>: {
        frontmatter,
        <span class="hljs-attr">fileContent</span>: mdxContent,
      },
    },
  }
}
</code></pre>
<p>The <code>[slug].js</code> page is now aware of the <code>fileContent</code> through the <code>heading</code> prop from the <code>TOC</code> component. So I need to modify it so it will accommodate the properties that the function returns.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { HeadingContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'./style/toc.styled'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TableOfContents</span>(<span class="hljs-params">{ headings }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HeadingContainer</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>In this article<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
        {headings.map(({ slug, title, id }) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{id}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`#${<span class="hljs-attr">slug</span>}`}&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">HeadingContainer</span>&gt;</span></span>
  )
}
</code></pre>
<p>For now, the component just renders the list of items in the array that is returned from the function, with no interactivity, no way to track which element is active, and many more things that I haven't been able to add for now.</p>
<h2 id="heading-how-to-add-click-and-scroll-based-states"><strong>How to Add Click and Scroll-based States</strong></h2>
<p>If there's anything I love about React, it is its ability to track state. I've seen how this works on other doc platforms – when you click on an item, it becomes active, when you scroll into the section where there's a heading tag, it becomes active.</p>
<p>A lot of people have different approaches to monitoring these states. I chose to go with the simplest one — changing the color — because, as usual, "I no like stress". The default text color in my component's UI is sorta "grey-ish", so when it is active, it becomes white.</p>
<p>I'll start with the snippets of the modification I made to the component with the <code>useState</code> hook, some DOM APIs, and the <code>getBoundingClientRect</code> web API. It's a lot – I know 😩. But, please stay with me, I'll try to break it down simply.</p>
<p>It is a common approach to have a default value — a boolean, string, or number — when we use the <code>useState</code> hook. In the snippet below, the component uses the <code>headings</code> prop to check if the length of the array isn't empty, is greater than zero, and sets the default state of the component to that of the first element.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> [active, setActive] = React.useState(
  headings.length &gt; <span class="hljs-number">0</span> ? headings[<span class="hljs-number">0</span>].slug : <span class="hljs-string">''</span>
)
</code></pre>
<p>If the array is empty, no element will have the active state style. For now, if you place an <code>onClick</code> attribute in the list element — like I did — and pass the <code>slug</code> as an argument, It'll toggle the style you have written in the <code>style</code> attribute.</p>
<pre><code class="lang-jsx">&lt;li
  key={index}
  onClick={<span class="hljs-function">() =&gt;</span> setActive(slug)}
  style={{
    <span class="hljs-attr">color</span>: active === slug ? <span class="hljs-string">'#fff'</span> : <span class="hljs-string">''</span>,
  }}
&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`#${<span class="hljs-attr">slug</span>}`}&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span>
&lt;/li&gt;
</code></pre>
<p>Handling the scroll state would require the use of React's <code>useEffect</code> hook because it contains all the lifeCycle methods —  <code>componentDidMount()</code>, <code>componentDidMount()</code>, and <code>componentWillUnmount()</code>. Here, I decided to track the scroll state by listening to the native scroll event with the DOM <code>EventTarget</code> interface.</p>
<p>The function <code>handleScroll</code> below maps the result we're getting from the <code>extractHeadings()</code> function by destructuring the <code>slug</code> property from the object. It proceeds to return all the elements containing a proper <code>id</code> attribute with <code>getElementById</code> and assigns the value to <code>headingElements</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> handleScroll = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> headingElements = headings.map(<span class="hljs-function">(<span class="hljs-params">{ slug }</span>) =&gt;</span>
    <span class="hljs-built_in">document</span>.getElementById(slug)
  )
  <span class="hljs-keyword">const</span> visibleHeadings = headingElements.filter(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span>
    isElementInViewport(el)
  )
  <span class="hljs-keyword">if</span> (visibleHeadings.length &gt; <span class="hljs-number">0</span>) {
    setActive(visibleHeadings[<span class="hljs-number">0</span>].id)
  }
}
</code></pre>
<p>Still in this function, the <code>visibleElements</code> is filtered from the array of <code>headingElements</code>, and the <code>isElementInViewport</code> function is used to check which heading element is currently in the viewport — this is possible with <code>getBoundingClientRect</code>, I'll get to that soon. </p>
<p>The function closes with a condition to set an active element if the length of the visible headings is greater than zero.</p>
<p>Now, I can go ahead to wrap this function in the Effect, initiate the cleanup of the scroll event, and pass the <code>headings</code> prop inside the dependency array. Then the Effect is only triggered when the <code>headings</code> prop changes.</p>
<pre><code class="lang-js">React.useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> handleScroll = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> headingElements = headings.map(<span class="hljs-function">(<span class="hljs-params">{ slug }</span>) =&gt;</span>
      <span class="hljs-built_in">document</span>.getElementById(slug)
    )
    <span class="hljs-keyword">const</span> visibleHeadings = headingElements.filter(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span>
      isElementInViewport(el)
    )
    <span class="hljs-keyword">if</span> (visibleHeadings.length &gt; <span class="hljs-number">0</span>) {
      setActive(visibleHeadings[<span class="hljs-number">0</span>].id)
    }
  }

  <span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'scroll'</span>, handleScroll)

  <span class="hljs-comment">// clean up the effect by removing the event listener</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">document</span>.removeEventListener(<span class="hljs-string">'scroll'</span>, handleScroll)
  }
}, [headings])
</code></pre>
<p><code>isElementInViewport</code> is the cherry on top of this feature. The function accepts an element, <code>el</code> as an argument, and it checks if its bounding rectangle (this sorta proves the box principle on the web to be correct, again) is inside the viewport of the browser.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> isElementInViewport = <span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> rect = el.getBoundingClientRect()
  <span class="hljs-keyword">return</span> (
    rect.top &gt;= <span class="hljs-number">0</span> &amp;&amp;
    rect.left &gt;= <span class="hljs-number">0</span> &amp;&amp;
    rect.bottom &lt;=
      (<span class="hljs-built_in">window</span>.innerHeight || <span class="hljs-built_in">document</span>.documentElement.clientHeight) &amp;&amp;
    rect.right &lt;= (<span class="hljs-built_in">window</span>.innerWidth || <span class="hljs-built_in">document</span>.documentElement.clientWidth)
  )
}
</code></pre>
<p>This is possible because of the <code>getBoundingClientRect</code> web API. The method returns an object containing the coordinates of the top, left, bottom and right edges of the element relative to the viewport.</p>
<p>When <code>getBoundingClientRect</code> is called, it returns an object containing the coordinates of the top, left, bottom, and right edges of a particular heading element relative to the viewport.</p>
<p>In the context of this feature, the element that is relative to the viewport is the heading element which is being retrieved using the <code>getElementById</code> method.</p>
<p>The function returns true if the top and left coordinates are greater than or equal to zero, and the bottom and right coordinates are less than or equal to the height and width of the viewport, respectively.</p>
<p>For the function to return <code>true</code>, we'd have to get the value of the viewport's height and width. That's why it is convenient to compare these values with <code>window.innerHeight</code> and <code>window.innerWidth</code> or <code>documentElement.clientHeight</code> and <code>documentElement.clientWidth</code>.</p>
<h2 id="heading-why-the-stress-intersectionobserver-fixes-this-issue"><strong>Why the Stress? IntersectionObserver Fixes This Issue</strong></h2>
<p>I know that going the <code>intersectionObserver</code> route would've saved me a lot of stress. But, I chose this approach nonetheless, because I wanted to understand the inner workings of how this feature is built by other people.</p>
<p>I think there's an intersection observer package that you can use to monitor scroll events in React applications, too. So you may not even need to go this route. But I want to share some of the reasons I decided to use this API, instead of the <code>IntersectionObserver</code>.</p>
<p>In terms of Accuracy, <code>getBoundingClientRect</code> returns a more accurate position of the element relative to the viewport, while <code>IntersectionObserver</code> uses an approximation based on the element's bounding box.</p>
<p>This means that <code>getBoundingClientRect</code> can be more precise for certain use cases, such as when you need to trigger an action as soon as the element enters the viewport — just like we're changing the active state of the list item in the component.</p>
<p>In terms of Browser compatibility, <code>IntersectionObserver</code> is a relatively new API, and its support by other browsers may not be available. But, <code>getBoundingClientRect</code> on the other hand is widely supported by modern browsers.</p>
<p>One advantage that <code>IntersectionObserver</code> has over <code>getBoundingClientRect</code> is in terms of performance. This is because the API uses an optimized algorithm that minimizes the amount of work needed to detect the changes in the intersection state when you are tracking so many elements.</p>
<p>The <code>getBoundingClientRect</code> API cannot handle so many elements.</p>
<h2 id="heading-wrapping-up"><strong>Wrapping Up</strong></h2>
<p>I know that a lot of people would still prefer to use <code>intersectionObserver</code>. But, I decided to go with this other approach because it opened my eyes to how <code>intersectionObserver</code> itself works under the hood, and most importantly, it suited my use case.</p>
<p>This is what the logic of the TOC component looks like — without the markup. Copy it and use it if you want.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { HeadingContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'./style/toc.styled'</span>

<span class="hljs-keyword">const</span> TableOfContents = <span class="hljs-function">(<span class="hljs-params">{ headings }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [active, setActive] = React.useState(
    headings.length &gt; <span class="hljs-number">0</span> ? headings[<span class="hljs-number">0</span>].slug : <span class="hljs-string">''</span>
  )

  React.useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> handleScroll = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> headingElements = headings.map(<span class="hljs-function">(<span class="hljs-params">{ slug }</span>) =&gt;</span>
        <span class="hljs-built_in">document</span>.getElementById(slug)
      )
      <span class="hljs-keyword">const</span> visibleHeadings = headingElements.filter(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span>
        isElementInViewport(el)
      )
      <span class="hljs-keyword">if</span> (visibleHeadings.length &gt; <span class="hljs-number">0</span>) {
        setActive(visibleHeadings[<span class="hljs-number">0</span>].id)
      }
    }

    <span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'scroll'</span>, handleScroll)
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">document</span>.removeEventListener(<span class="hljs-string">'scroll'</span>, handleScroll)
    }
  }, [headings])

  <span class="hljs-keyword">const</span> isElementInViewport = <span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> rect = el.getBoundingClientRect()
    <span class="hljs-keyword">return</span> (
      rect.top &gt;= <span class="hljs-number">0</span> &amp;&amp;
      rect.left &gt;= <span class="hljs-number">0</span> &amp;&amp;
      rect.bottom &lt;=
        (<span class="hljs-built_in">window</span>.innerHeight || <span class="hljs-built_in">document</span>.documentElement.clientHeight) &amp;&amp;
      rect.right &lt;= (<span class="hljs-built_in">window</span>.innerWidth || <span class="hljs-built_in">document</span>.documentElement.clientWidth)
    )
  }

  <span class="hljs-keyword">return</span> <span class="hljs-comment">// component markup</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TableOfContents
</code></pre>
<p>If you read up to this point, please share this article. Thanks as you do so. You can also read up on the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">getBoundingClientRect() web API</a> if you want to get an in-depth understanding</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Categories to a Ruby on Rails Application ]]>
                </title>
                <description>
                    <![CDATA[ By Sampurna Chapagain Creating a category page is essential for most web applications these days. Different kinds of applications like blogs, ecommerce sites, movie streaming platforms, and many others have category features. This article will show h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-categories-to-ruby-on-rails-apps/</link>
                <guid isPermaLink="false">66d460f2868774922c885012</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ruby on Rails ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 01 Mar 2023 23:31:16 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/fcccat.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sampurna Chapagain</p>
<p>Creating a category page is essential for most web applications these days. Different kinds of applications like blogs, ecommerce sites, movie streaming platforms, and many others have category features.</p>
<p>This article will show how to add categories to Ruby on Rails applications.</p>
<p>This is a beginner-friendly tutorial, so you can follow along even if you have very basic Ruby on Rails knowledge.</p>
<h2 id="heading-how-to-generate-the-blog-scaffold">How to Generate the Blog Scaffold</h2>
<p>Let's first create a new Rails project. Here, we will be using Rails version 6.1.7 and Ruby version 3.0. You can create the new Rails project using the command below:</p>
<pre><code class="lang-ruby">rails new blog_categories
</code></pre>
<p>Now, let's create the new blog scaffold using the following command:</p>
<pre><code class="lang-ruby">rails g scaffold blogs <span class="hljs-symbol">title:</span>string <span class="hljs-symbol">description:</span>text
</code></pre>
<p>This command will create all the necessary files for us to work with the blog's <code>CRUD</code> operations with two database fields <code>title</code> and <code>description</code>. Here, the <code>title</code> is the <code>string</code> field and the <code>description</code> is the <code>text</code> field.</p>
<p>You now need to run the migration with the <code>rails db:migrate</code> command.</p>
<p>Now, head up to your terminal, start the server, and visit the <code>/blogs</code> page. You can see the following result on the browser.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/blog.gif" alt="Image" width="600" height="400" loading="lazy">
<em>creating blogs</em></p>
<h2 id="heading-how-to-generate-the-categories-scaffold">How to Generate the Categories Scaffold</h2>
<p>Now, we have come to the main part of the tutorial which is to add categories to our blog articles.</p>
<p>For that, let's create the categories scaffold now with the command below:</p>
<pre><code class="lang-ruby">rails g scaffold categories <span class="hljs-symbol">name:</span>string
</code></pre>
<p>This command will create <code>CRUD</code> for the <code>categories</code>.</p>
<p>Also, you will need to run <code>rails db:migrate</code> to update the schema.</p>
<h3 id="heading-how-to-add-an-association-between-the-blog-and-category-models">How to add an association between the blog and category models</h3>
<p>Next, you need to add an association between the <code>blog</code> and <code>category</code> models.</p>
<p>In <code>Blog.rb</code>:</p>
<pre><code class="lang-ruby"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Blog</span> &lt; ApplicationRecord</span>
    belongs_to <span class="hljs-symbol">:category</span>
<span class="hljs-keyword">end</span>

And <span class="hljs-keyword">in</span> <span class="hljs-string">`Category.rb`</span>:

<span class="hljs-string">``</span><span class="hljs-string">`ruby
class Category &lt; ApplicationRecord
    has_many :blogs
end


Now, you need to add the `</span>category_id<span class="hljs-string">` to the `</span>blogs<span class="hljs-string">` table since each blog is associated with a category. You can go to your terminal and add the following migration:

`</span><span class="hljs-string">``</span>ruby
rails g migration add_category_id_to_blogs
</code></pre>
<p>This will create a new migration. You need to add the following code in this migration file:</p>
<pre><code class="lang-ruby">add_column <span class="hljs-symbol">:blogs</span>, <span class="hljs-symbol">:category_id</span>, <span class="hljs-symbol">:integer</span>
</code></pre>
<p>Rails has its own set of conventions and rules. And it's smart enough to recognize that this migration is to add a new database column named <code>category_id</code> to the <code>blogs</code> table.</p>
<p>You now need to pass <code>category_id</code> as a strong parameter in the <code>blogs</code> controller.</p>
<pre><code class="lang-ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">blog_params</span></span>
    params.<span class="hljs-keyword">require</span>(<span class="hljs-symbol">:blog</span>).permit(<span class="hljs-symbol">:title</span>, <span class="hljs-symbol">:content</span>, <span class="hljs-symbol">:category_id</span>)
<span class="hljs-keyword">end</span>
</code></pre>
<p>Now, let's create a few categories from the browser.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/categories.gif" alt="Image" width="600" height="400" loading="lazy">
<em>creating categories from the browser</em></p>
<p>You could also add some validation while creating category. For that, you can update the <code>category.rb</code> file with the following code:</p>
<p>```ruby
class Category &lt; ApplicationRecord
    validates :name, presence: true, uniqueness: true
    has_many :blogs
end</p>
<p>This will add few validations as shown in the GIF below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/validations.gif" alt="Image" width="600" height="400" loading="lazy">
<em>adding validations while creating categories</em></p>
<h3 id="heading-how-to-display-categories-as-a-dropdown-in-the-new-blogs-page">How to display categories as a dropdown in the new blogs page</h3>
<p>Now that you can create both blogs and categories, you'll need to display all categories as a dropdown when someone visits the new blog page.</p>
<p>The code for that is shown below:</p>
<p>```ruby</p>
<div>
    &lt;%= form.label :category %&gt;
    &lt;%= form.select :category_id, options_for_select(Category.all.map { |category| [category.name, category.id]})%&gt;
</div>

<p>The <code>select</code> tag creates the dropdown selection box. The <code>options_for_select</code> takes a few arguments. </p>
<p>First, it maps through all categories and returns an array of <code>[c.category, c.id]</code>. The category <code>name</code> gets displayed as a dropdown and the category <code>id</code> of any category the user selects gets passed to the controller. The controller then performs the database insertion for blogs. </p>
<p>You can go to the new blogs URL and create blogs with categories.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/blog1-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>creating blogs with categories</em></p>
<h3 id="heading-listing-each-categorys-blogs">Listing each category's blogs</h3>
<p>The next part of this tutorial will show you how to list all categories and all blogs associated with each category. We will be displaying this on the categories index page.</p>
<p>For that, you need to add some code in the categories index view.</p>
<p>```ruby</p>
<p id="notice">&lt;%= notice %&gt;</p>

<h1 id="heading-categories">Categories</h1>


    &lt;% @categories.each do |category| %&gt;
      
        &lt;% category.blogs.each do |blog| %&gt;
          
        &lt;% end %&gt;
      
    &lt;% end %&gt;
  <table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Blogs</th>
      <th></th>
    </tr>
  </thead>

  <tbody><tr>
        <td>&lt;%= category.name %&gt;</td><td>&lt;%= blog.title %&gt;</td></tr></tbody>
</table>

<p><br></p>
<p>&lt;%= link_to 'New Category', new_category_path %&gt;</p>
<p>Here, it's iterating through all categories in order to display each category's name. After we are done with displaying the category name, the next part is to display all blogs associated with each category. So, you can iterate through <code>category.blogs</code> from which you can get the blog records. </p>
<p>If you visit the <code>/category</code> route, you can see all categories with their blogs as displayed in the GIF below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/cat_blogs.gif" alt="Image" width="600" height="400" loading="lazy">
<em>listing each category with blogs</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you learned how you can add categories in your Ruby on Rails applications using one-to-many associations.</p>
<p>If you liked this article, please consider <a target="_blank" href="https://www.buymeacoffee.com/SamChapagain">buying me a coffee</a> ☕.</p>
<p>You can find me on <a target="_blank" href="https://twitter.com/saam_codes">Twitter</a> for various content related to Web Development.</p>
<p>Thanks for reading.</p>
<p>Happy Coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Custom Ghost CMS Theme ]]>
                </title>
                <description>
                    <![CDATA[ Ghost CMS is a platform specifically designed for bloggers and writers. Using Ghost, you can quickly get a blog website up and running.  Ghost targets primarily writers and all the features are specifically built for writing.  Ghost's new dashboard g... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-ghost-cms-theme/</link>
                <guid isPermaLink="false">66d038a8d6e6a35e9f8e6978</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ghost ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rajdeep Singh ]]>
                </dc:creator>
                <pubDate>Wed, 04 Jan 2023 15:20:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/ghost-theme-development-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Ghost CMS is a platform specifically designed for bloggers and writers. Using Ghost, you can quickly get a blog website up and running. </p>
<p>Ghost targets primarily writers and all the features are specifically built for writing. </p>
<p>Ghost's new dashboard gives you a user-friendly interface, and beginners can easily understand the functionality. In addition, Ghost's free tutorials will help you if you have any problems. </p>
<p><strong>Some cool Ghost features:</strong></p>
<ol>
<li>Open-source</li>
<li>Membership support</li>
<li>Rich text editor (Koenig editor)</li>
<li>Newsletters</li>
<li>Email subscriber support</li>
<li>Login functionality support</li>
<li>Integration plugin </li>
<li>Analytics dashboard</li>
<li>Inbuilt comment support</li>
<li>Inbuilt search support</li>
<li>Inbuilt search functionality</li>
<li>SEO and different types of metadata support social media</li>
<li>Custom theme design</li>
</ol>
<p>You can check out all <a target="_blank" href="https://github.com/frontendweb3/fastest">the code available for this project on GitHub here</a>.</p>
<h2 id="heading-heres-what-well-cover">Here's what we'll cover:</h2>
<ol>
<li><a class="post-section-overview" href="#heading-self-hosting-vs-hosting-with-ghost">Self-Hosting vs Hosting with Ghost</a></li>
<li><a class="post-section-overview" href="#heading-what-are-the-drawbacks-of-ghost-cms">What Are the Drawbacks of Ghost?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-the-ghost-cms">How to Install the Ghost CMS</a></li>
<li><a class="post-section-overview" href="#heading-understanding-the-ghost-folder-structure">Understanding the Ghost Folder Structure</a></li>
<li><a class="post-section-overview" href="#heading-understanding-the-ghost-theme-folder-structure">Understanding the Ghost Theme Folder Structure</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-new-theme-with-the-npm-cli-tool">How to Create a New Theme with the npm CLI Tool</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-new-ghost-theme-from-scratch">How to Create a New Ghost Theme from Scratch</a></li>
<li><a class="post-section-overview" href="##how-to-install-ghost-cli-globally">How to Install ghost-cli Globally</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-ghost-locally">How to install Ghost locally</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-tailwind-css">How to Configure Tailwind CSS</a></li>
<li><a class="post-section-overview" href="#heading-other-important-commands-in-the-ghost-cli">Other Important Commands in the Ghost CLI</a></li>
<li><a class="post-section-overview" href="#how-to-write-the-code-for-our-custom-ghost-theme">How to Write the Code for Our Custom Ghost Theme</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-theme-configuration-in-packagejson">How to add theme configuration in package.json</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-theme-helpers">How to Use Theme Helpers</a></li>
<li><a class="post-section-overview" href="#heading-what-is-the-partials-folder">What is the Partials Folder?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-default-page">How to Create a Default Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-an-index-page">How to Create an Index Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-posts-page">How to Create a Posts Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-informational-pages">How to Create Information Pages</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-an-author-page">How to Create an Author Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-tags-page">How to Create a Tags Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-an-error-page">How to Create an Error Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enable-comments">How to Enable Comments</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-search">How to Set Up Search</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h2 id="heading-self-hosting-vs-hosting-with-ghost">Self-hosting vs Hosting with Ghost</h2>
<p>Ghost provides two ways to create/host your website:</p>
<ol>
<li>Self-hosting</li>
<li>With the Ghost cloud platform</li>
</ol>
<h3 id="heading-self-hosting">Self-hosting</h3>
<p>If you choose to self-host, you'll host your website on any cloud platform like <a target="_blank" href="https://cloud.google.com/">Google cloud</a>, <a target="_blank" href="https://aws.amazon.com/">AWS cloud,</a> <a target="_blank" href="https://azure.microsoft.com/">Azure cloud</a>, <a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a>, and so on. These are some of the most used cloud platforms in the market. </p>
<p>Most cloud platforms come with one click to deploy solutions. This means you can deploy your Ghost blog with a single click. </p>
<p>Before deploying your Ghost blog, you should compare all cloud platforms based on pricing before choosing one.  </p>
<p>Self-hosting your Ghost blog is free, and you do not need to pay anything to the Ghost platform. You'll just pay your cloud provider. </p>
<h3 id="heading-hosting-with-the-ghost-cloud-platform">Hosting with the Ghost Cloud Platform</h3>
<p>If you choose to host with Ghost, they'll help create the blog and host it on the Ghost platform itself. The Ghost team handles all the maintenance and security. You won't have to worry about updating Ghost and any themes you're using – the Ghost staff will handle that for you.</p>
<p>Self-hosting focuses more on developers, while hosting with the Ghost platform targets anyone who doesn't know about computers and programming.</p>
<p>Ghost hosting comes with a paid plan – it's not free. But they give you 14 day free trial period, after which you shift automatically into a paid plan.</p>
<h3 id="heading-what-should-you-choose-paid-or-self-hosting">What should you choose, paid or self-hosting?</h3>
<p>In my experience, hosting with the Ghost platform is the best solution for beginner developers, non-developers, and writers. The Ghost team handles everything for you. You do not worry about traffic, security, or maintenance and do not need to update the Ghost CMS. This lets you focus on writing. </p>
<p>As a developer, I always recommended that you self-host Ghost. I have run my self-hosted Ghost blog with Google Cloud for two years with a Bitnami one-click deployment.</p>
<p>After six months, I'd used up my $200 free credit, and then I started to pay monthly to Google Cloud hosting.</p>
<p>For a non-technical person, I highly recommended using the Ghost (pro) cloud platform and as well any other platform that provides Ghost-based cloud and shares hosting.</p>
<p>I found a <a target="_blank" href="https://geekflare.com/ghost-hosting-platforms/">list of Ghost-hosting platforms</a> on the internet. Perhaps one of these will solve your hosting issues or questions. If you plan to deploy Ghost with the G<a target="_blank" href="https://officialrajdeepsingh.dev/tags/ghost-cms/">oogle Cloud platform</a>, I have an article on that.</p>
<h2 id="heading-what-are-the-drawbacks-of-ghost-cms">What are the Drawbacks of Ghost CMS?</h2>
<p>The biggest drawback of Ghost is that web performance can feel slow. If you want good web performance, you'll likely need to use a CDN for media (images, videos, and PDFs) and also for CSS and JavaScript.  </p>
<p>The second biggest drawback is cost. I've been running my blog with Ghost for two years, and I pay 10 to 20 times extra to Google Cloud for hosting as a self-deploy. </p>
<p>My website has 4000 to 5000 active monthly users, and I pay 20 times extra. Because of this, I shifted my website to Hugo. </p>
<p>Now I still have 4000 to 5000 active users on the website, and I pay zero money to Netlify.</p>
<h3 id="heading-the-solution-for-developers">The Solution for Developers</h3>
<p>The best solution for developers is to use Ghost as a backend and, with the REST API, choose any JavaScript framework like Next.js, Fresh, Astro, and so on. </p>
<p>There are a lot of frameworks that can help you build a static website. In addition, static websites are fast and deploy with zero JavaScript.</p>
<p>In this method, you may not use all Ghost's features, but you can save a lot of money. Still, building the website with a JavaScript framework takes a lot of time just to run the essential version of the website. </p>
<p>My solution only works well for a small team. So if your team has a lot of writers and submits many articles in a single day, I'd recommend sticking with Ghost CMS as a frontend and backend.</p>
<p>Ghost version 5.0 is 20% faster than the old version. Suppose you use Ghost and want to design your own custom theme – then this article is for you. Let's get started.</p>
<h2 id="heading-how-to-install-the-ghost-cms">How to Install the Ghost CMS</h2>
<p>How you install Ghost CMS changes according to your operating system. We'll discuss installation for all operating systems in this guide. You can install Ghost with npm, yarn, and Docker.</p>
<p>Now let's look at how to install Ghost for:</p>
<ol>
<li>Windows, Linux, and macOS</li>
<li>Docker image</li>
</ol>
<h2 id="heading-how-to-install-ghost-on-windows-linux-and-macos">How to Install Ghost on Windows, Linux, and macOS</h2>
<p>Setting up the Ghost theme development environment in Windows and macOS is a straightforward process. But it's best if you've installed the npm or yarn package manager. If you don't have Node.js, npm, and yarn, yolu'll need to install them – Node.js comes with preinstalled npm and yarn. </p>
<p>To install Ghost CMS globally and locally, follow these basic steps:</p>
<h3 id="heading-how-to-install-ghost-cli-globally">How to install ghost-CLI globally</h3>
<p>First, you can install <code>ghost-cli</code> globally on your machine using npm or yarn. Here are the commands:</p>
<pre><code class="lang-bash">npm install ghost-cli@latest -g
    OR
yarn global add ghost-cli@latest
</code></pre>
<h3 id="heading-how-to-install-ghost-locally">How to install Ghost locally</h3>
<p>Next, when your ghost-CLI installation is complete, then run the <code>ghost local</code> command in your terminal. It looks like this:</p>
<pre><code class="lang-bash">ghost install <span class="hljs-built_in">local</span>
</code></pre>
<p>The command output looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/carbon--1-.png" alt="Ghost cms folder structure" width="600" height="400" loading="lazy">
<em>Ghost cms folder structure</em></p>
<p><strong>Note</strong>: you'll need to run the <code>ghost install local</code> command in an empty folder. Otherwise, you'll face an error:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ghost-not-empty-error.png" alt="Not empty directory error with ghost-cli" width="600" height="400" loading="lazy">
<em>Not empty directory error with ghost-cli</em></p>
<p>To start the local development server, run the <code>ghost start</code> command in your terminal.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ghost-start-command.png" alt="Ghost start command " width="600" height="400" loading="lazy">
<em>Ghost start command</em></p>
<p>If you get an error when running <code>ghost start</code> in Ubuntu, run the following command: <code>ghost start --no-setup-linux-user</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/not-readable-by-other-user.png" alt="The directory is not readable by other users' errors in ghost cms." width="600" height="400" loading="lazy">
<em>The directory is not readable by other users' error in Ghost CMS.</em></p>
<h2 id="heading-how-to-set-up-your-environment-using-a-docker-image">How to Set Up Your Environment Using a Docker Image</h2>
<p>Docker is also a great way to set up a theme development or production environment for Ghost. The Ghost team provides an official <a target="_blank" href="https://hub.docker.com/_/ghost">Ghost Docker image</a> on Docker Hub. </p>
<p>To start the setup, you'll need the <code>docker-compose.yml</code> file in your root project folder. Then run the <code>docker-compose up</code> command in your terminal. </p>
<pre><code>version: <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services</span>:
  blog:
    image: ghost
    <span class="hljs-attr">restart</span>: always
    <span class="hljs-attr">ports</span>:
      - <span class="hljs-number">8080</span>:<span class="hljs-number">2368</span>
    <span class="hljs-attr">volumes</span>:
      - ./custom-ghost-theme:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/custom-ghost-theme/
    environment:
      url: http:<span class="hljs-comment">//localhost:808</span>
      NODE_ENV: development
</code></pre><p>In the volume section, you pass your file. In my case, I added a specific file in my Ghost theme folder.</p>
<pre><code>version: <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services</span>:
  blog:
    image: ghost
    <span class="hljs-attr">restart</span>: always
    <span class="hljs-attr">ports</span>:
      - <span class="hljs-number">8080</span>:<span class="hljs-number">2368</span>
    <span class="hljs-attr">volumes</span>:
      - ./assets:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/assets
      - ./partials:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/partials
      - ./author.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/author.hbs
      - ./<span class="hljs-keyword">default</span>.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/<span class="hljs-keyword">default</span>.hbs
      - ./error<span class="hljs-number">-404.</span>hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/error<span class="hljs-number">-404.</span>hbs
      - ./error.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/error.hbs
      - ./gulpfile.js:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/gulpfile.js
      - ./index.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/index.hbs
      - ./package-lock.json:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/package-lock.json
      - ./package.json:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/package.json
      - ./page.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/page.hbs
      - ./post.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/post.hbs
      - ./query.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/query.hbs
      - ./tag.hbs:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/tag.hbs
      - ./readme.md:<span class="hljs-regexp">/var/</span>lib/ghost/content/themes/fastest/readme.md
    <span class="hljs-attr">environment</span>:
      url: http:<span class="hljs-comment">//localhost:8080</span>
      NODE_ENV: development
</code></pre><p>In your custom-ghost-theme folder, you need the <code>index.hbs</code>, <code>post.hbs</code>, and <code>package.json</code> files to start theme development. But, you'll get an error when you activate your theme in the Ghost dashboard without requiring a file.</p>
<p><strong>Here's the GitHub repo if you want to follow along:</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/officialrajdeepsingh/ghostthemewithdocker">https://github.com/officialrajdeepsingh/ghostthemewithdocker</a></div>
<h4 id="heading-errors">Errors</h4>
<p>In Ubuntu (22.04) or any other Linux distros, you'll get the <code>Message: The directory /home/rajdeepsingh/ is not readable by other users on the system</code> error. This means yours is old. So update your <code>ghost-cli</code> then run the <code>ghost start</code> command in your folder.</p>
<h2 id="heading-understanding-the-ghost-folder-structure">Understanding the Ghost Folder Structure</h2>
<p>The Ghost folder structure has three main folders and one file. They are:</p>
<ol>
<li>The <code>config.development.json</code> file contains the configuration for Ghost development.</li>
<li>The <code>current</code> folder is a link (symbolic link) that targets the install version.</li>
<li>The <code>version</code> folder contains all versions of Ghost cms.</li>
<li>The content folder is the main folder containing our database file, settings, theme, images, media, and so on.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ghost-folder-sturture.png" alt="Ghost cms folder Structure" width="600" height="400" loading="lazy">
<em>Ghost CMS folder Structure</em></p>
<p>The folder structure might change according to the operating system but the <code>content</code> folder is the same in every operating system.</p>
<p>The content folder contains all the important files for Ghost.  They are:</p>
<ol>
<li>The data folder contains an SQLite3 database file. Ghost, by default, uses the SQLite3 database.</li>
<li>Files, images, and media folders contain all files which writers upload.</li>
<li>The public folder contains all public CSS and JavaScript files – for example, card and member JavaScript and CSS files.</li>
<li>Finally, the settings folder contains all the settings, for example, the <code>router.xml</code> file.</li>
<li>The theme folder contains all files and folders used to develop the theme.</li>
</ol>
<h2 id="heading-understanding-the-ghost-theme-folder-structure">Understanding the Ghost Theme Folder Structure</h2>
<p>You can build a new custom theme store in the <code>content/theme</code> folder. To develop a new theme, you'll always need to create a new folder with the theme name and store all files in the theme name folder.</p>
<pre><code><span class="hljs-comment">// theme structure</span>

content 
content/theme
content/theme/my-theme-name
content/theme/my-theme-name/index.hbs
content/theme/my-theme-name/post.hbs
content/theme/my-theme-name/package.json

<span class="hljs-comment">// rest of file created in my-theme-name folder</span>
</code></pre><p>Ghost CMS uses <strong>handlebars</strong> to build a Ghost theme. There are a number of files but only three files are required:</p>
<ol>
<li><code>index.hbs</code> in the main file (required) to design the home page of the website.</li>
<li><code>post.hbs</code> the file (required)  is used to read and design the full article.</li>
<li><code>package.json</code> file (required)  is used for Node.js config, and it also uses the theme name, description, version, custom config, and so on.</li>
<li>The <code>default.hbs</code> file is used to build the layout of the theme.</li>
<li>The assets folder contains all the JavaScript, CSS, fonts, and image files.</li>
<li>The partials folder helps divide files into small partials (parts) for better code readability.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/theme-struture.png" alt="Ghost theme folder structure" width="600" height="400" loading="lazy">
<em>Ghost theme folder structure</em></p>
<h2 id="heading-how-to-create-a-new-theme-with-the-npm-cli-tool">How to Create a New Theme with the npm CLI Tool</h2>
<p>The easiest way to start a new Ghost theme is with the <a target="_blank" href="https://www.npmjs.com/package/create-ghost-theme">create-ghost-theme CLI</a>. I built it, and I maintain it. The create-ghost-theme CLI helps you create the following folder structure that we'll discuss next. But currently, it only supports <strong>Tailwind CSS</strong>. </p>
<p>First, we'll create a new theme with the create-ghost-theme CLI and restart the Ghost CMS local server again.</p>
<h3 id="heading-folder-structure">Folder structure</h3>
<p>After creating a new theme with <a target="_blank" href="https://www.npmjs.com/package/create-ghost-theme">create-ghost-theme CLI</a>, your folder structure looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ghost-theme-cli.png" alt="create-ghost-theme cli folder structure" width="600" height="400" loading="lazy">
<em>create-ghost-theme cli folder structure</em></p>
<h3 id="heading-understanding-the-new-website-layout">Understanding the New Website Layout</h3>
<p>After creating the theme with create-ghost-theme CLI, your theme looks like this. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/default-hbs.png" alt="Image" width="600" height="400" loading="lazy">
<em>index.hbs</em></p>
<p>Your website reading page will look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/index-hbs.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Your new tag page looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/tag-hbs.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-a-new-ghost-theme-from-scratch">How to Create a New Ghost Theme from Scratch</h2>
<p>When you're learning about Ghost theme development, my recommendation is to start creating a new theme from scratch. Then you can use the CLI tool I just showed you. This will be a lot easier for you.</p>
<p>So now, that's what we're going to cover in-depth: how to create a new Ghost CMS theme from scratch. </p>
<h3 id="heading-requirements">Requirements:</h3>
<p>To create a new theme, you'll need two libraries: the first is <code>ghost-cli</code> and the second is Tailwind CSS. </p>
<p>Here's what we'll go over in the coming sections:</p>
<ol>
<li>How to install <code>Ghost-cli</code> globally</li>
<li>How to configure Tailwind CSS</li>
<li>How to understand more commands in the Ghost CLI</li>
<li>Finally, we'll write the code</li>
</ol>
<h3 id="heading-how-to-install-ghost-cli-globally-1">How to Install ghost-cli Globally</h3>
<p>We went over how to do this above, but in case you need a reminder here it is:</p>
<p>First, you can install <code>ghost-cli</code> globally on your machine using npm or yarn. Here are the commands:</p>
<pre><code class="lang-bash">npm install ghost-cli@latest -g
    OR
yarn global add ghost-cli@latest
</code></pre>
<h3 id="heading-how-to-configure-tailwind-css">How to Configure Tailwind CSS</h3>
<p>Tailwind CSS is a powerful CSS library for designing the front end of a website. And you can easily use it with Ghost.</p>
<p>Install Tailwind CSS in your theme folder like this:</p>
<pre><code class="lang-bash">npm install -D tailwindcss postcss autoprefixer
</code></pre>
<p>After Tailwind and another package have been successfully installed, then run the following command to configure Tailwind for your theme development:</p>
<pre><code class="lang-bash">npx tailwindcss init
</code></pre>
<p>The <code>tailwindcss init</code> command creates a <code>tailwind.config.js</code> file. Here's what you'll see:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">content</span>: [],
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<p>Config your template path in the content section, so Tailwind CSS tracks the CSS classes. Then compile those classes in the production file.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">content</span>: [<span class="hljs-string">"*.hbs"</span>,<span class="hljs-string">"partials/*.hbs"</span>],
  <span class="hljs-attr">darkMode</span>: <span class="hljs-string">'class'</span>,
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<p>Create a <code>main.css</code> or <code>dev.css</code> and use any other file name to create the file for Tailwind directives. Then paste the following tailwind CSS directives code into the file:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<p>Create the script for Tailwind CSS to check all the classes then create a production-ready CSS file for your theme. </p>
<pre><code class="lang-json">    <span class="hljs-string">"scripts"</span>: {
        <span class="hljs-attr">"start"</span>: <span class="hljs-string">"npx tailwindcss -i ./assets/css/dev.css -o ./assets/build/css/build.css --watch"</span>
    },
</code></pre>
<p>The last step is to link the production-ready CSS file to your theme like this:</p>
<pre><code class="lang-handlebars">&lt;head&gt;
    {{!-- link production ready css file  --}}
    &lt;link rel="stylesheet" href="{{asset 'build/css/build.css'}}" /&gt;
&lt;/head&gt;
</code></pre>
<p>The one problem you might face when you enable Tailwind CSS in a Ghost theme is that refreshing your site in the development process is manual. When you change anything related to Tailwind classes, you'll need manually refresh your website again. I haven't found a solution yet, but you can use the live server for that for now.</p>
<h3 id="heading-other-important-commands-in-the-ghost-cli">Other Important Commands in the Ghost CLI</h3>
<p>There are a number of other commands you'll use often when working in the Ghost CLI. Let's go through them now. Here's what we'll discuss:</p>
<ol>
<li>ghost stop</li>
<li>ghost ls</li>
<li>ghost doctor</li>
<li>ghost uninstall</li>
<li>ghost version</li>
<li>ghost restart</li>
<li>ghost update</li>
<li>ghost version</li>
<li>ghost --help</li>
</ol>
<h4 id="heading-how-to-use-the-ghost-stop-command">How to use the ghost stop command</h4>
<p>The <code>ghost stop</code> command stops the currently running instance.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-stop.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-ls-command">How to use the ghost ls command</h4>
<p>The <code>ghost ls</code> command prints the current installs instance list in your machine.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-ls.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-doctor-command">How to use the ghost doctor command</h4>
<p>The <code>ghost doctor</code> command checks the system's health to see if everything is fine before running the <code>ghost install</code> or <code>ghost update</code> command. </p>
<p>If you face any errors in Ghost, you can also use the ghost doctor command to check the errors.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-doctor.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-uninstall-command">How to use the ghost uninstall command</h4>
<p>The <code>ghost uninstall</code> command removes the Ghost instance and related configuration files as well.</p>
<h4 id="heading-how-to-check-the-ghost-version">How to check the Ghost version</h4>
<p>You can use the <code>ghost version</code> command to check your currently installed version of Ghost.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-version.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-restart-command">How to use the ghost restart command</h4>
<p>The <code>ghost restart</code> command restarts your currently running Ghost instance.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-restart.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-update-command">How to use the ghost update command</h4>
<p>The ghost update command updates your old Ghost version to the new version.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost-update.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-use-the-ghost-help-command">How to use the ghost --help command</h4>
<p>The <code>ghost --help</code> command prints a help page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/ghost---help.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-finally-well-write-the-code">Finally, we'll write the code</h3>
<p>Now we get to start writing the code. Here's what we'll cover in the coming sections:</p>
<ol>
<li>How to add theme configuration in <code>package.json</code> [required]</li>
<li>How to use theme helpers</li>
<li>What is the partials folder?</li>
<li>How to create a default page</li>
<li>How to create an index page [required]</li>
<li>How to create a posts page [required]</li>
<li>How to create informational page</li>
<li>How to create a tags page</li>
<li>How to set up comments</li>
<li>How to enable search</li>
</ol>
<h3 id="heading-how-to-add-theme-configuration-in-packagejson">How to add theme configuration in package.json</h3>
<p>The <code>package.json</code> file is the main file where you add or change the theme name, description, and custom configuration for the theme. </p>
<p>The <strong>first step</strong> is to create <code>package.json</code> file and add the theme name, description, version, and additional configuration. </p>
<p>The following properties are used by Ghost themes: <code>name</code>, <code>description</code>, <code>version</code>, <code>engines</code>, <code>card_assets</code>, <code>license</code>, <code>author</code>, <code>keywords</code>, <code>screenshots</code>, and <code>config</code> in the <code>package.json</code> file. </p>
<p>The most important properties are <code>name</code>, <code>description</code>, <code>version</code>,  <code>engines</code>, <code>card_assets</code>, and <code>config</code>. Here's what this looks like in the code:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"fastest"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Fastest ghost cms base theme. Fastest is light weight, modern open-source theme"</span>,
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.7"</span>,
    <span class="hljs-attr">"engines"</span>: {
        <span class="hljs-attr">"ghost"</span>: <span class="hljs-string">"&gt;=5.0.0"</span>
    },
    <span class="hljs-attr">"license"</span>: <span class="hljs-string">"MIT"</span>,
    <span class="hljs-attr">"scripts"</span>: {
        <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"gulp"</span>,
        <span class="hljs-attr">"start"</span>: <span class="hljs-string">"npx tailwindcss -i ./assets/css/dev.css -o ./assets/build/css/build.css --watch"</span>
    },
    <span class="hljs-attr">"author"</span>: {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Rajdeep Singh"</span>,
        <span class="hljs-attr">"email"</span>: <span class="hljs-string">"officialrajdeepsingh@gmail.com"</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://officialrajdeepsingh.dev"</span>
    },
    <span class="hljs-attr">"keywords"</span>: [
        <span class="hljs-string">"ghost"</span>,
        <span class="hljs-string">"theme"</span>,
        <span class="hljs-string">"blog"</span>,
        <span class="hljs-string">"light weight"</span>,
        <span class="hljs-string">"ghost-theme"</span>
    ],
    <span class="hljs-attr">"screenshots"</span>: {
        <span class="hljs-attr">"desktop"</span>: [
            <span class="hljs-string">"assets/dark.png"</span>,
            <span class="hljs-string">"assets/light.png"</span>
        ],
        <span class="hljs-attr">"mobile"</span>: <span class="hljs-string">"assets/mobile.png"</span>
    },
    <span class="hljs-attr">"config"</span>: {
        <span class="hljs-attr">"posts_per_page"</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">"card_assets"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"image_sizes"</span>: {},
        <span class="hljs-attr">"custom"</span>: {
            <span class="hljs-attr">"linkedin_url"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"text"</span>,
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"None"</span>
            },
            <span class="hljs-attr">"github_url"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"text"</span>,
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"None"</span>
            },
            <span class="hljs-attr">"instagram_url"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"text"</span>,
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"None"</span>
            },
            <span class="hljs-attr">"copyright"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"text"</span>,
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"Copy right by Rajdeep Singh"</span>
            },
            <span class="hljs-attr">"copyright_url"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"text"</span>,
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"https://officialrajdeepsingh.dev/pages/terms-and-conditions/"</span>
            },
            <span class="hljs-attr">"adsense_enable"</span>: {
                <span class="hljs-attr">"type"</span>: <span class="hljs-string">"select"</span>,
                <span class="hljs-attr">"options"</span>: [
                    <span class="hljs-string">"Disable"</span>,
                    <span class="hljs-string">"Enable"</span>
                ],
                <span class="hljs-attr">"default"</span>: <span class="hljs-string">"Disable"</span>
            }
        }
        },
    <span class="hljs-attr">"devDependencies"</span>: {
        <span class="hljs-attr">"@tailwindcss/typography"</span>: <span class="hljs-string">"^0.5.8"</span>,
        <span class="hljs-attr">"autoprefixer"</span>: <span class="hljs-string">"^10.4.13"</span>,
        <span class="hljs-attr">"cssnano"</span>: <span class="hljs-string">"^5.0.17"</span>,
        <span class="hljs-attr">"gscan"</span>: <span class="hljs-string">"^4.22.0"</span>,
        <span class="hljs-attr">"gulp"</span>: <span class="hljs-string">"4.0.2"</span>,
        <span class="hljs-attr">"gulp-autoprefixer"</span>: <span class="hljs-string">"^8.0.0"</span>,
        <span class="hljs-attr">"gulp-concat"</span>: <span class="hljs-string">"^2.6.1"</span>,
        <span class="hljs-attr">"gulp-cssnano"</span>: <span class="hljs-string">"^2.1.3"</span>,
        <span class="hljs-attr">"gulp-livereload"</span>: <span class="hljs-string">"4.0.2"</span>,
        <span class="hljs-attr">"gulp-sourcemaps"</span>: <span class="hljs-string">"^3.0.0"</span>,
        <span class="hljs-attr">"gulp-uglify"</span>: <span class="hljs-string">"^3.0.2"</span>,
        <span class="hljs-attr">"postcss"</span>: <span class="hljs-string">"^8.4.20"</span>,
        <span class="hljs-attr">"tailwindcss"</span>: <span class="hljs-string">"^3.2.4"</span>
    }
}
</code></pre>
<p>You can learn more about <a target="_blank" href="https://ghost.org/docs/themes/content/">card_assets</a> and <a target="_blank" href="https://ghost.org/docs/themes/structure/#packagejson">config</a> for the theme. The config section helps add configuration for Ghost. You can also add more <a target="_blank" href="https://ghost.org/docs/themes/custom-settings/">custom configuration</a> for Ghost and enable and disable it with the Ghost UI. </p>
<p>To check all configurations, go to Ghost &gt; Settings &gt; Design &gt; and click Site-wide. There you can check all configuration lists provided by the theme developer.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/customconfig.png" alt="custom config enable and disable in ghost cms" width="600" height="400" loading="lazy">
<em>custom config enable and disable in ghost cms</em></p>
<h3 id="heading-how-to-use-theme-helpers">How to Use Theme Helpers</h3>
<p>The Ghost team provide lots of helpful functions to add additional functionality to the Ghost theme with <a target="_blank" href="https://handlebarsjs.com/">handlebars</a>. Some of the functionality by default comes with handlebars and other functionality is built by the Ghost team and maintained by the community.  </p>
<p>The Ghost team uses handlebars to build the entire Ghost CMS and theme. Basically, handlebars.js is a template language that helps you build both static and dynamic websites. </p>
<p>There are lots of Ghost helpers like <a target="_blank" href="https://ghost.org/docs/themes/helpers/foreach/">foreach</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/get/">get</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/if/">if</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/is/">is</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/match/">match</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/config/">@config</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/comments/">comments</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/navigation/">navigation</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/post/">post</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/total_members/">total_members</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/total_paid_members/">total_paid_members</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/block/">block</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/asset/">asset</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/ghost_head_foot/">ghost_head</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/ghost_head_foot/">ghost_foot</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/pagination/">pagination</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/partials/">partials</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/body_class/">body_class</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/block/">block</a>, <a target="_blank" href="https://ghost.org/docs/themes/helpers/search/">search</a> and many more. </p>
<p>You can read about all of the <a target="_blank" href="https://ghost.org/docs/themes/helpers/">helpers on the official docs</a>. You can also copy-paste some of the code so you do not need to remember. </p>
<h3 id="heading-what-is-the-partials-folder">What is the Partials Folder?</h3>
<p>The partials folder is like a component folder where you define all components for your theme. Basically, components are reusable code that you can reuse as often as you need. In the theme structure, we call these partials. All the partials are created with handlebars.js.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/partials.png" alt="partials for ghost theme" width="600" height="400" loading="lazy">
<em>partials for ghost theme</em></p>
<p>I create more than 24 partials for my Ghost theme and you can easily reuse them across websites. You can use partials with the following syntax: <code>{{&gt; your-partials-file-name}}</code>.</p>
<h3 id="heading-how-to-create-a-default-page">How to Create a Default Page</h3>
<p>First, we need to built a <code>default.hbs</code> file. The <code>default.hbs</code> file helps us build a layout for the website. Here's the code:</p>
<pre><code class="lang-handlebars">&lt;!DOCTYPE html&gt;

&lt;html class="dark scroll-smooth overflow-x-hidden" lang="{{@site.locale}}"&gt;

&lt;head&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;

    {{!-- link production ready css file  --}}
    &lt;link rel="stylesheet" href="{{asset 'build/css/build.css'}}" /&gt;
   {{!-- ghost header --}}
      {{ghost_head}}

&lt;/head&gt;


&lt;body class="{{body_class}} bg-white dark:bg-slate-800 dark:text-white antialiased scroll-smooth "&gt;

  {{!-- partials/header --}}
    {{&gt; header}}

    &lt;main class="mt-6 flex flex-col"&gt;

{{!-- Render other pages  --}}
        {{{body}}}

    &lt;/main&gt;
{{!-- partials/footer --}}
    {{&gt; footer}}
    {{&gt; banner}}

{{!-- ghost header --}}
    {{ghost_foot}}

    &lt;script src="{{asset 'build/js/main.js'}}"&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>Let's see what's going on here:</p>
<ol>
<li><code>{{meta_title}}</code> provides the title from the website.</li>
<li>The <code>@site</code> is a global variable and you can access a title with <code>{{@site.title}}</code>. </li>
<li>Include a Ghost <code>{{ghost_head}}</code> in the head tag.</li>
<li>Include a Ghost <code>{{ghost_foot}}</code> in the footer tag.</li>
<li>Inserted all other templates with the <code>{{{body}}}</code> tag in index.hbs, post.hbs, and so on.</li>
<li>All other templates get inserted in index.hbs, post.hbs, and so on.</li>
<li>Include dynamic CSS classes with <code>{{body_class}}</code> in the <code>&lt;body&gt;</code> tag</li>
<li>Add footer partials in the default <code>{{&gt; footer}}</code> file</li>
<li>Add header partials in default <code>{{&gt; header}}</code> file</li>
<li>Include assets from the <code>{{asset "build/tailwind.css"}}</code> folder.</li>
</ol>
<h3 id="heading-how-to-create-an-index-page">How to Create an Index Page</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/index.png" alt="Create index.hbs in the ghost theme" width="600" height="400" loading="lazy">
<em>Create <code>index.hbs</code> in the Ghost theme</em></p>
<p>The index page is the main page of the website. You can create a similar index page with the following code:</p>
<pre><code class="lang-handlebars">{{!--  Add default.hbs layout file --}}
{{!&lt; default}} 

{{!-- loop all the posts and show on home page --}}

{{#foreach posts}} 

{{!--  check condition defined in config section in package.json and add adsense code after every third post --}}
    {{#has number="nth:3" }} 
        {{#match @custom.adsense_enable "Enable" }} 
            {{&gt; ads}}
        {{/match}}
    {{/has}}

    {{!-- partials/postCard.hbs --}}
    {{&gt; postCard }}

    {{/foreach}}

    {{!-- Add pagination --}}
    {{pagination}}

{{!--  check condition defined in config section in package.json and add adsense --}}
    {{#match @custom.adsense_enable "Enable"}}
        {{&gt; ads}}
    {{/match}}

{{!-- newsletter partials --}}
    {{&gt; newsletter}}
</code></pre>
<p>You can access all posts with a for each loop and pass them to the partials with the <code>{{&gt; postCard}}</code> template. The <code>@custom.adsense_enable</code> is a custom config written in the <code>package.json</code> file and used in the theme to check that the website owner has enabled AdSense on-site or not. The custom config enables you to go to Ghost &gt; Settings &gt; Design &gt; and to click on Site-wide and enable Adsense.</p>
<h3 id="heading-how-to-create-a-posts-page">How to Create a Posts Page</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/post.png" alt="Create post.hbs in the ghost theme" width="600" height="400" loading="lazy">
<em>Create <code>post.hbs</code> in the Ghost theme</em></p>
<p>The posts page is where readers will read your articles on your site. You can create a posts page with the following code.</p>
<pre><code class="lang-handlebars">{{!&lt; default}}

{{#post}}


{{!-- Pass reading_time time to authors partials with block , learn more about about block https://ghost.org/docs/themes/helpers/block/ --}}
{{#contentFor "fastestReadingTime"}}

 &lt;p class="text-slate-600 dark:text-slate-400 text-xs xs:text-xs sm:text-xs md:text-sm lg:text-sm xl:text-sm 2xl:text-sm "&gt;
    {{reading_time}}
 &lt;/p&gt;

{{/contentFor}}

{{#match @custom.adsense_enable "Enable"}}

    {{&gt; ads}}

{{/match}}

&lt;article class="{{post_class}} w-6/6 p-2"&gt;

    &lt;!-- Heading section --&gt;

    &lt;div class="m-auto mb-4 w-6/6 xs:w-5/6 sm:w-5/6 md:w-5/6 lg:w-5/6 xl:w-5/6 2xl:w-5/6"&gt;
        &lt;h1 class="mt-8 text-3xl xs:text-4xl sm:text-5xl md:text-5xl xl:text-5xl 2xl:text-5xl"&gt;
             {{title}} 
        &lt;/h1&gt;
        &lt;p class="text-slate-600 dark:text-slate-500 mt-2 text-1xl"&gt;
            {{excerpt}}
        &lt;/p&gt;
    &lt;/div&gt;

    &lt;!--  Author card partials/authors --&gt;
    {{&gt; authors}}



    &lt;!-- article thumbnail with partials/authors --&gt;
    {{&gt; featureImage}}


    &lt;!-- article body --&gt;
    &lt;div class="prose-lg prose-neutral m-auto p-2 my-10 w-10/12"&gt;
        {{content}}
    &lt;/div&gt;




&lt;/article&gt;


    {{!-- partials/comment --}}
    {{&gt; comment}}


{{!-- Add adsense  --}}
{{#match @custom.adsense_enable "Enable"}}

    {{&gt; ads}}

{{/match}}

{{!-- Show related posts --}}
{{#get "posts" filter="authors:{{primary_author.slug}}+id:-{{id}}" limit="3" include="authors"}}

{{!-- if post is available then show it --}}
{{#if posts}}

&lt;h2 class="mt-10 m-auto text-left w-5/6 text-xl xs:text-1xl sm:text-3xl md:text-4xl xl:text-5xl 2xl:text-6xl"&gt;
    Read more
&lt;/h2&gt;

{{!-- loop all post --}}
{{#foreach posts}}
    {{&gt; postCard }}
{{/foreach}}

{{/if}}

{{/get}}

{{!-- Add adsense  --}}
{{#match @custom.adsense_enable "Enable"}}

    {{&gt; ads}}

{{/match}}

{{/post}}

{{!-- newsletter partials --}}
{{&gt; newsletter}}
</code></pre>
<p>The fastestReadingTime block is to pass the reading time to the author partials. The <code>@custom.adsense_enable</code> is a custom config written in the <code>package.json</code> file and used in the theme to check that the website owner has enabled AdSense on-site or not. The custom config enables you to go to Ghost &gt; Settings &gt; Design &gt; and to click to Site-wide and enable Adsense.</p>
<h3 id="heading-how-to-create-informational-pages">How to Create Informational Pages</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/page.png" alt="Create page.hbs in the ghost theme" width="600" height="400" loading="lazy">
<em>Create <code>page.hbs</code> in the ghost theme</em></p>
<p>The <code>page.hbs</code> file helps you create informational pages for your website. For example, you can create an about, contact, privacy policy, or disclaimer page on your site. </p>
<pre><code class="lang-handlebars">{{!&lt; default}} 

{{#post}} 

    {{!--Pass reading_time time to authors partials with block , learn more about about block https://ghost.org/docs/themes/helpers/block/     --}}
    {{#contentFor "fastestReadingTime"}}

    &lt;p class="text-grey-600 text-xs xs:text-xs sm:text-xs md:text-sm lg:text-sm xl:text-sm 2xl:text-sm "&gt;
        {{reading_time}}
    &lt;/p&gt;
    {{/contentFor}}

{{!-- Add adsense base of if enable on theme  --}}
    {{#match @custom.adsense_enable "Disable"}}

        {{&gt; ads}}

    {{/match}}

    &lt;article class="{{post_class}}  w-6/6 p-2"&gt;

        &lt;!-- Heading section --&gt;
        &lt;div class=" m-auto mb-16 w-6/6 xs:w-5/6 sm:w-5/6 md:w-5/6 lg:w-5/6 xl:w-5/6 2xl:w-5/6"&gt;
            &lt;h1 class="text-gray-800 mt-8 text-3xl xs:text-4xl sm:text-4xl md:text-5xl xl:text-6xl 2xl:text-8xl"&gt;
                {{title}}
            &lt;/h1&gt;
            &lt;p class="text-gray-600 text-xl xs:text-xl sm:text-xl md:text-1xl xl:text-2xl 2xl:text-2xl"&gt;
                {{excerpt}}
            &lt;/p&gt;
        &lt;/div&gt;

        &lt;!--  partials/authors --&gt;
        {{&gt; authors}}


        {{!--  partials/featureImage  --}}
        {{&gt; featureImage}}



        &lt;!-- article body --&gt;
        &lt;div class=" prose-xl prose-neutral m-auto p-2 my-10 w-10/12"&gt;
            {{content}}
        &lt;/div&gt;



    &lt;/article&gt;


    {{!-- Add adsense  --}}
    {{#match @custom.adsense_enable "Disable"}}

        {{&gt; ads}}

    {{/match}}

    {{/post}}
</code></pre>
<p>The fastestReadingTime block is to pass the reading time to the author partials. The <code>@custom.adsense_enable</code> is a custom config written in the <code>package.json</code> file and used in the theme to check that the website owner has enabled AdSense on-site or not. The custom config enables you to go to Ghost &gt; Settings &gt; Design &gt; and to click to Site-wide and enable Adsense.</p>
<h3 id="heading-how-to-create-an-author-page">How to Create an Author Page</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/author.png" alt="Create author.hbs in the ghost theme" width="600" height="400" loading="lazy">
<em>Create <code>author.hbs</code> in the ghost theme</em></p>
<p>Author pages let you describe the author. You can show the author's name, bio, and related articles.</p>
<pre><code class="lang-handlebar">{{!&lt; default}}

{{#author}}

{{!--  Author Section pass with block learn more about about block https://ghost.org/docs/themes/helpers/block/ --}}
   {{#contentFor "authorName"}}
      {{name}}
    {{/contentFor}}

&lt;div class="container mt-20 mb-16 flex flex-col justify-between mx-auto"&gt;

    &lt;div class="flex flex-col mt-6 w-6/6 xs:w-5/6 sm:w-5/6  md:w-3/6 lg:w-3/6 xl:w-3/6 2xl:w-3/6 xs:mt-6 sm:mt-6 md:mt-6 lg:mt-0 xl:mt-0 2xl:mt-0 "&gt;

        &lt;h1 class="text-3xl mt-5 xs:text-3xl sm:text-3xl md:text-4xl xl:text-5xl 2xl:text-6xl"&gt; {{name}} &lt;/h1&gt;

        {{#if bio}}
            &lt;p class="mt-0 xs:mt-0 sm:mt-0 md:mt-1 lg:mt-3 xl:mt-3 2xl:mt-3 text-md"&gt;
                {{bio}}
            &lt;/p&gt;
        {{/if}}

        &lt;ul class="flex flex-row my-3"&gt;

            &lt;li class="text-md"&gt;{{location}}&lt;/li&gt;

               {{#if facebook}}

                    &lt;li class="mx-3 text-sm flex items-center"&gt;
                        &lt;a target="_blank" href="https://facebook.com/{{facebook}}" &gt;

                        {{!-- Pass partials/Icons/facebook --}}
                            {{&gt; Icons/facebook}}

                        &lt;/a&gt;
                    &lt;/li&gt;

                {{/if}}
                {{#if twitter}} 
                    &lt;li class="mx-3 text-sm flex items-center"&gt;
                        &lt;a target="_blank" href="https://twitter.com/{{twitter}}" &gt;

                        {{!-- Pass partials/Icons/twitter --}}
                            {{&gt; Icons/twitter}}

                        &lt;/a&gt;
                    &lt;/li&gt;
                {{/if}}
                {{#if website}} 
                    &lt;li class="mx-3 text-sm flex items-center"&gt;
                        &lt;a target="_blank" href="{{website}}" &gt;

                            {{!-- Pass partials/Icons/website --}}
                            {{&gt; Icons/website}}

                        &lt;/a&gt;
                    &lt;/li&gt;
                {{/if}}
        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/div&gt;


{{!--  get posts related to author base on author Id --}}
    {{#get "posts" limit="all" filter="authors:{{slug}}+id:-{{id}}" order="published_at desc"   }}

        {{#if posts}}
                {{#foreach posts}}
                    {{&gt; authorCard}}
                {{/foreach}}
        {{/if}}

    {{/get}}

{{/author}}
</code></pre>
<h3 id="heading-how-to-create-a-tags-page">How to Create a Tags Page</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/tag.png" alt="Create tag.hbs in the ghost theme " width="600" height="400" loading="lazy">
<em>Create tag.hbs in the ghost theme</em></p>
<p>You can use the <code>tag.hbs</code> file to show articles related to the tag used.</p>
<pre><code>{{!&lt; <span class="hljs-keyword">default</span>}}


{{#tag}}

&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"container m-auto mt-32  mb-16  w-5/6 xs:w-5/6 sm:w-5/6 md:w-5/6 lg:w-5/6 xl:w-5/6 2xl:w-5/6"</span>&gt;

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">" text-gray-800 text-4xl xs:text-5xl sm:text-6xl md:text-7xl xl:text-8xl 2xl:text-9xl"</span>&gt;</span>{{name}}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>

    {{#<span class="hljs-keyword">if</span> description}}
        &lt;p <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">" text-gray-600 text-xl xs:text-xl sm:text-xl md:text-1xl xl:text-2xl 2xl:text-2xl"</span>&gt;
            {{description}}       
        &lt;/p&gt;
    {{/<span class="hljs-keyword">if</span>}}


&lt;/div&gt;

{{!--  get posts related to tag base on  tag slug --}}

    {{#get <span class="hljs-string">"posts"</span> include=<span class="hljs-string">"authors,tags"</span> limit=<span class="hljs-string">"3"</span> filter=<span class="hljs-string">"tag:{{slug}}"</span> <span class="hljs-keyword">as</span> |related|}}

   {{!-- loop posts base on article --}}
        {{#foreach related}}

    {{!--  check condition define <span class="hljs-keyword">in</span> config section <span class="hljs-keyword">in</span> package.json and add adsense code after every third post --}}
            {{#has number=<span class="hljs-string">"nth:3"</span>  }}
                {{#match @custom.adsense_enable <span class="hljs-string">"Enable"</span>}}
                    {{&gt; ads}}
                {{/match}}
            {{/has}}

        {{!-- partials/postCard.hbs --}}
            {{&gt; postCard }}

        {{/foreach}}

    {{/get}}

{{/tag}}
</code></pre><h3 id="heading-how-to-create-an-error-page">How to Create an Error Page</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/404error-1.png" alt="Create error.hbs in the ghost theme" width="600" height="400" loading="lazy">
<em>Create <code>error.hbs</code> in the ghost theme</em></p>
<p>You use the <code>error.hbs</code> file to show when any errors occur on the website. Error pages help your website not break in production. The most common error is a 404 (not found) error.</p>
<pre><code>{{!&lt; <span class="hljs-keyword">default</span>}} 
&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"flex flex-col m-auto p-10 w-5/6 xs:w-5/6 sm:w-5/6 md:w-5/6 lg:w-5/6 xl:w-5/6 2xl:w-5/6"</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"font-size: 10.8rem;"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-black text-center"</span>&gt;</span>
        {{statusCode}}
    <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-4xl -m-6 text-center"</span>&gt;</span>
        {{message}}
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-center w-32 m-auto my-20 p-3 bg-black text-white items-center rounded-full"</span>&gt;</span>
        Home
    <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span>
&lt;/div&gt;
</code></pre><h3 id="heading-how-to-enable-comments">How to Enable Comments</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/comment-1.png" alt="enable comment in the ghost theme" width="600" height="400" loading="lazy">
<em>enable comments in the ghost theme</em></p>
<p>Ghost 5 officially supports <a target="_blank" href="https://ghost.org/docs/themes/helpers/comments/">the commenting system</a> (it's built-in) and you can just enable comments on the theme by copying and pasting the code – you never need any configuration. Ghost itself handles all the configurations. Here's the code:</p>
<pre><code class="lang-handlebars">&lt;div class="m-auto my-8 w-10/12"&gt;
    &lt;p class="text-right text-xs xs:text-xs sm:text-xs md:text-sm lg:text-sm xl:text-sm 2xl:text-sm "&gt;
        Before comment read our &lt;a style='text-decoration: underline' href="https://officialrajdeepsingh.dev/terms-and-conditions/"&gt;term and condition &lt;/a&gt;
    &lt;/p&gt;
    &lt;div class="mt-5 mb-5 p-4"&gt;

   {{!--  Enable comment on theme --}}
       {{comments}}
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h3 id="heading-how-to-set-up-search">How to Set Up Search</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/12/serach-bar.png" alt="enable the search bar in the ghost theme" width="600" height="400" loading="lazy">
<em>enable the search bar in the ghost theme</em></p>
<p>Ghost 5 comes with the <a target="_blank" href="https://ghost.org/docs/themes/helpers/search/">official support of search functionality</a>. You do not need any other configuration. Just paste the following code into your theme and the search functionally will start working on your site. </p>
<pre><code class="lang-handlebars">{{!-- partials/Icons/search --}}
&lt;button class="gh-search" data-ghost-search&gt;{{&gt; Icons/search}}&lt;/button&gt;
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building a theme with Ghost is a relatively straightforward process compared to WordPress. The Ghost team has created well-defined documentation that you can easily follow as a beginner with examples. </p>
<p>They also provide many ready-made components, like search functionality, amp page, comments, and so on.</p>
<p>You can create your Ghost theme by copy-pasting the code. For beginner developers, it might seem a bit complicated, but you'll get the hang of it with some time and work. </p>
<p>The Ghost team has created a well-defined folder structure for theme development. It is the easiest way to manage the theme development process. You can also use npm packages to enhance the development process and add more functionality to the theme. In my theme, I use tailwind CSS and the Gulp package to speed up the development process.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Static Markdown Blog with Deno and Deploy It ]]>
                </title>
                <description>
                    <![CDATA[ Deno is a runtime for JavaScript and TypeScript. The creator of Node.js built it, and while Node is built with C and C++, Deno is built with the Rust language. You might be wondering what some of the main differences between Node and Deno are. Well, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-blog-with-deno/</link>
                <guid isPermaLink="false">66d038acd6e6a35e9f8e697d</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ markdown ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rajdeep Singh ]]>
                </dc:creator>
                <pubDate>Tue, 13 Sep 2022 16:46:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/Create-a-Static-Markdown-Blog-with-Deno--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Deno is a runtime for JavaScript and TypeScript. The creator of Node.js built it, and while Node is built with C and C++, Deno is built with the Rust language.</p>
<p>You might be wondering what some of the main differences between Node and Deno are. Well, Rust is a low-level language similar to C and Java. It helps make Deno super fast, and Deno is also more secure than Node.</p>
<p>In this article, we will build a static markdown blog with Deno in less than five minutes. In the end, we'll deploy the markdown blog with Deno deploy.</p>
<p>We'll use Deno's third-party <a target="_blank" href="https://deno.land/x/blog">blog package</a> created by Ryan Dahl and another contributor for the blog</p>
<p>With the Deno blog module, you can create a fantastic blazing-fast blog. Then you can set up and deploy the blog with two lines of code. And it takes less than five minutes to configure it.</p>
<h3 id="heading-what-is-markdown">What is markdown?</h3>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> is a lightweight markup language. It helps create consistently formatted text. To start working in markdown, you need an IDE that supports markdown and you'll need to create a file with a <code>.md</code> extension. Markdown typically supports written documents, blogs, and so on. </p>
<p>Some examples of documents written in Markdown are GitHub and npm READMEs, the React.js, and many more.</p>
<p>The Deno blog module (Package) comes with markdown support and lets you create a static blog. This module comes with lots of features, like:</p>
<ol>
<li>Markdown support.</li>
<li>Auto refresh. Any change in a markdown file automatically builds and reloads your website in the browser.</li>
<li>You can customize the header and add comments and a footer section.</li>
<li>It supports SEO, SEO markup, and an inbuilt feed (sitemap).</li>
<li>iFrames support with markdown files.</li>
<li>It has built-in Preact, TypeScript, and Tailwind CSS support.</li>
<li>It allows multiple authors </li>
<li>It has middleware and redirects pathname support </li>
<li>It comes with server-side Google Analytics support</li>
</ol>
<p>Here's a <a target="_blank" href="https://deno-markdown-blog.deno.dev/">demo of the blog we'll build and deploy:</a></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/denoblogdemo.gif" alt="deno blog demo" width="600" height="400" loading="lazy">
<em>deno blog demo</em></p>
<p>All the code is <a target="_blank" href="https://github.com/officialrajdeepsingh/deno-markdown-blog">available on GitHub</a>.</p>
<h3 id="heading-here-are-the-steps-well-follow">Here are the steps we'll follow:</h3>
<ol>
<li>How to install and setup the blog</li>
<li>How to understand the folder structure</li>
<li>How to start the local developer server</li>
<li>How to add more configuration to the blog.</li>
<li>How to deploy with Deno</li>
</ol>
<h2 id="heading-how-to-install-and-setup-the-blog">How to Install and Setup the Blog</h2>
<p>First, you'll need to install the Deno blog module. The blog module comes with the init command to create a new blog setup. It looks like this:</p>
<pre><code>deno run -r --allow-read --allow-write https:<span class="hljs-comment">//deno.land/x/blog/init.ts my-deno-demo-blog-name</span>
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2022/09/create-deno-blog.png" alt="Create a blog setup with the deno blog module" width="600" height="400" loading="lazy">
<em>Create a blog setup with the deno blog module</em></p>
<h2 id="heading-how-to-understand-the-folder-structure">How to Understand the Folder Structure</h2>
<p>The beauty of Deno is that you only need a few files to start a project. For the markdown blog, you need only four files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/deno-blog-folder-structure--1-.png" alt="deno folder structure" width="600" height="400" loading="lazy">
<em>deno folder structure</em></p>
<p>Let's go through each file in the above folder structure:</p>
<ul>
<li>In the <code>deno.jsonc</code> file you add tasks and the importMap file. Tasks are similar to scripts in Node, and in the importMap section, you pass a JSON file that contains all your import packages from Deno.</li>
<li>The <code>import_map.json</code> file contains imports of all packages which you need to run your project.</li>
<li>The <code>posts</code> folder contains all markdown files.</li>
<li>The <code>main.tsx</code> file contains all configurations for the blog module.</li>
</ul>
<h2 id="heading-how-to-start-the-local-developer-server">How to Start the Local Developer Server</h2>
<p>After installation is complete, run the local development server with the <code>deno task dev</code> command.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/run-deno-blog.png" alt="Run deno local development server" width="600" height="400" loading="lazy">
<em>Run deno local development server</em></p>
<h2 id="heading-how-to-add-more-configuration-to-the-blog">How to Add More Configuration to the Blog</h2>
<p>The blog module default comes with the following configuration in the <code>main.tsx</code> file. You can easily change blog configurations according to your requirements.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.tsx</span>

<span class="hljs-keyword">import</span> blog, { ga, redirects, h } <span class="hljs-keyword">from</span> <span class="hljs-string">"blog"</span>;

blog({
  title: <span class="hljs-string">"My Blog"</span>,
  description: <span class="hljs-string">"This is my new blog."</span>,
  <span class="hljs-comment">// header: &lt;header&gt;Your custom header&lt;/header&gt;,</span>
  <span class="hljs-comment">// section: &lt;section&gt;Your custom section&lt;/section&gt;,</span>
  <span class="hljs-comment">// footer: &lt;footer&gt;Your custom footer&lt;/footer&gt;,</span>
  avatar: <span class="hljs-string">"https://deno-avatar.deno.dev/avatar/blog.svg"</span>,
  avatarClass: <span class="hljs-string">"rounded-full"</span>,
  author: <span class="hljs-string">"An author"</span>,

  <span class="hljs-comment">// middlewares: [</span>

    <span class="hljs-comment">// If you want to set up Google Analytics, paste your GA key here.</span>
    <span class="hljs-comment">// ga("UA-XXXXXXXX-X"),</span>

    <span class="hljs-comment">// If you want to provide some redirections, you can specify them here,</span>
    <span class="hljs-comment">// pathname specified in a key will redirect to pathname in the value.</span>
    <span class="hljs-comment">// redirects({</span>
    <span class="hljs-comment">//  "/hello_world.html": "/hello_world",</span>
    <span class="hljs-comment">// }),</span>

  <span class="hljs-comment">// ]</span>
});
</code></pre>
<h3 id="heading-custom-configuration">Custom configuration</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/denoblog.png" alt="Demo of custom configuration" width="600" height="400" loading="lazy">
<em>Demo of custom configuration</em></p>
<p>With custom configuration, you can make your website look however you want – even like the example above. In addition, you can quickly add more custom configurations to your blog. </p>
<p>For example, you can change the default header, footer, title, author, theme, custom style, links, section, and so on. Here's some code to do that:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// main.tsx</span>

<span class="hljs-comment">/** <span class="hljs-doctag">@jsx </span>h */</span>
<span class="hljs-keyword">import</span> blog, { h } <span class="hljs-keyword">from</span> <span class="hljs-string">"blog"</span>;
<span class="hljs-keyword">import</span> { Section } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Section.jsx'</span>;

blog({
  <span class="hljs-attr">author</span>: <span class="hljs-string">"Rajdeep singh"</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"Hello, my name is Rajdeep Singh"</span>,
  <span class="hljs-attr">description</span>: <span class="hljs-string">"Nice to meet you"</span>,
  <span class="hljs-attr">avatar</span>:<span class="hljs-string">"assets/logos/profile.jpg"</span>,
  <span class="hljs-attr">avatarClass</span>: <span class="hljs-string">"rounded-full"</span>,
  <span class="hljs-attr">coverTextColor</span>:<span class="hljs-string">"white"</span>,
  <span class="hljs-attr">links</span>: [
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"Email"</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">"mailto:officialrajdeepsingh@gmail.com"</span> },
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"GitHub"</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">"https://github.com/officialrajdeepsingh"</span> },
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"Twitter"</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">"https://twitter.com/Official_R_deep"</span> },
    { <span class="hljs-attr">title</span>: <span class="hljs-string">"Linkedin"</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.linkedin.com/in/officalrajdeepsingh/"</span> },
  ],
  <span class="hljs-attr">lang</span>: <span class="hljs-string">"en"</span>,
  <span class="hljs-attr">favicon</span>: <span class="hljs-string">"favicon.ico"</span>,
  <span class="hljs-attr">section</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Section</span>/&gt;</span></span>,
  theme:<span class="hljs-string">"auto"</span>,
  <span class="hljs-attr">cover</span>:<span class="hljs-string">"assets/logos/backgroundbanner.png"</span>,
  <span class="hljs-attr">ogImage</span>: {
    <span class="hljs-attr">url</span>: <span class="hljs-string">"http://localhost:8000/assets/logos/Frame.png"</span>,
    <span class="hljs-attr">twitterCard</span>:  <span class="hljs-string">"summary_large_image"</span> 
  },
  <span class="hljs-attr">style</span>:<span class="hljs-string">".markdown-body ul, .markdown-body ol { list-style: disc !important;}"</span>
});
</code></pre>
<p>Markdown files support various types of frontMatter. The most common and widely used fontMatter types are:</p>
<ol>
<li>YAML: YAML is identified by opening and closing <code>---</code>.</li>
<li>JSON: JSON is identified by '<code>{</code>' and '<code>}</code>'.</li>
<li>TOML: TOML is identified by opening and closing <code>+++</code>.</li>
</ol>
<p>The most common frontMatter is <a target="_blank" href="https://yaml.org/">YAML</a>. The YAML frontMatter support markdown file is everywhere. But <strong>the Deno blog module only supports yml frontMatter</strong>. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/markdownfile.png" alt="yml front matter example" width="600" height="400" loading="lazy">
<em>yml front matter example</em></p>
<p>The Markdown file is divided into two sections. The first section is the header (frontMatter), and the second is the body section.</p>
<p>The header section contains all metadata for the article. All the metadata is written inside three dashes (<code>---</code>) both opening and closing – for example, post title, tag, description, publish date, and so on. </p>
<p>Finally, in the body section, you write your article body and explain it.</p>
<pre><code class="lang-markdown">// hello-world.md

---
author : "Rajdeep Singh"
publish<span class="hljs-emphasis">_date : "2020-11-10T11:42:46Z"
description : "Easy Ways Add CSS in Next.js #SeriesPart2"
og:image : "assets/images/next.js-add-css-code.jpg"
tags : ["Next.js", "Next", "Next.js Framework", "Next.js Tutorial", "React.js", "react.js tutorial"]
title : "How To Add CSS In Next js?"
allow_</span>iframes: true
cover<span class="hljs-emphasis">_html: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"assets/images/next.js-add-css-code.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"How To Add CSS In Next js"</span> /&gt;</span></span>
pathname: "hello-world"
---

First blog post created with the Deno blog package. It is an amazing package you can use to create markdown blogs with Tailwind CSS. 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"560"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"315"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://www.youtube-nocookie.com/embed/3NR9Spj0DmQ"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"YouTube video player"</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">allow</span>=<span class="hljs-string">"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"</span> <span class="hljs-attr">allowfullscreen</span>&gt;</span></span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span></span>


```javascript
console.log("hello world")</span>
</code></pre>
<pre><code>
The Deno blog <span class="hljs-built_in">module</span> supports the following YML FrontMatter fields <span class="hljs-keyword">in</span> a markdown file:

<span class="hljs-number">1.</span> author (string): The <span class="hljs-string">`author`</span> contains the names <span class="hljs-keyword">of</span> the authors. For example, <span class="hljs-string">`author : "Rajdeep singh , deno"`</span>
<span class="hljs-number">2.</span> publish_date(<span class="hljs-built_in">Date</span>):  The <span class="hljs-string">`publish_date`</span> is required <span class="hljs-keyword">for</span> the article.
<span class="hljs-number">3.</span> description (string): The <span class="hljs-string">`description`</span> is required <span class="hljs-keyword">for</span> the description.
<span class="hljs-number">4.</span> og:image(string): The <span class="hljs-string">`og:image`</span> is not required. It is used <span class="hljs-keyword">for</span> <span class="hljs-string">`&lt;meta property="og:image" content="assets/images/Title-tag-In-HTML-5.jpg"&gt;`</span>
<span class="hljs-number">5.</span> tags(string[]): The <span class="hljs-string">`tags`</span> are just keywords used <span class="hljs-keyword">for</span> SEO. They<span class="hljs-string">'re not compulsory.
6. title(string): The `title` is required for the heading.
7. allow_iframes( boolean ): the `allow_iframes` allows you to use `iframe` HTML.
8. pathname( string ): pathname is not required. For example, `http://yourdomain.com/hello-world` after your domain `hello-world` is your pathname
9. cover_html(string): The `cover_html` contains the HTML for the blog.</span>
</code></pre><p>author : "Rajdeep Singh , Rajvinder singh"
publish_date : "2022-03-20T13:09:24Z"
description : "Npm install command help to install package from npmjs.org"
og:image : "assets/images/npm-init-command-1.png"
tags : ["npm-test", "npm-cli", "npm install command"]
title : "What is the npm install command?"
allow_iframes: true
pathname:"/how-is-npm-install-command"
cover_html: <img src="assets/images/npm-init-command-1.png" alt="npm command" width="600" height="400" loading="lazy"></p>
<pre><code>
These are all the supported fields by YML frontMatter <span class="hljs-keyword">for</span> markdown files:
</code></pre><p>title, author, publish_date, description, og:image, tags, allow_iframes, pathname, cover_html</p>
<pre><code>
This is the required field <span class="hljs-keyword">for</span> markdown files:
</code></pre><p>title
```</p>
<p>Without a title filed, the blog module produces an error <code>Uncaught TypeError: Cannot read properties of undefined (reading 'snippet')</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/undefiend-snippet-error.png" alt="error: Uncaught TypeError: Cannot read properties of undefined (reading 'snippet')" width="600" height="400" loading="lazy">
<em>Error: Uncaught TypeError: Cannot read properties of undefined (reading 'snippet')</em></p>
<h2 id="heading-how-to-deploy-your-blog-with-deno">How to Deploy Your Blog with Deno</h2>
<p>The final step is to deploy our static blog in Deno. Currently, the Deno blog module only supports Deno for deployment. But Deno deploy provides a similar interface to Netlify and Vercel. So you can easily understand the dashboard if you've worked with those tools before.</p>
<p>To deploy a new blog on Deno, you need two things. The first is an account on <a target="_blank" href="https://deno.com/deploy">Deno deploy</a>, and the second is a GitHub account. With a GitHub repository to help manage your articles, it is a straightforward process similar to <a target="_blank" href="https://vercel.com/">Vercel</a> and <a target="_blank" href="https://www.freecodecamp.org/news/p/00c3cfd3-6447-48dc-a915-804b26bf056e/netlify.com">Netlify</a>.</p>
<h3 id="heading-deployment-steps">Deployment steps:</h3>
<p>Here are the steps to deploy your blog with Deno deploy (we'll go through each one in detail below):</p>
<ol>
<li>First, login to your account on Deno deploy</li>
<li>Click to create a new project</li>
<li>Configure the GitHub repository and environment variables</li>
<li>Deploy the static blog</li>
</ol>
<h3 id="heading-login-to-your-account-on-deno-deploy">Login to your account on Deno deploy</h3>
<p>First, go to the <a target="_blank" href="https://deno.com/deploy">Deno deploy</a> website and create a new account if you don't have one already. If you do, then login to your account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/deno-deploy-dashboard.png" alt="deno deploy website" width="600" height="400" loading="lazy">
<em>deno deploy website</em></p>
<h3 id="heading-click-to-create-a-new-project">Click to create a new project</h3>
<p>After successfully logging in, you can now access the Deno dashboard and click on the "+ <strong>new project</strong>" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/create-project-with-deno.png" alt="create a new project with deno deploy" width="600" height="400" loading="lazy">
<em>Create a new project with deno deploy</em></p>
<h3 id="heading-configure-the-github-repository-and-environment-variables">Configure the GitHub repository and environment variables</h3>
<p>After clicking on the <strong>new project</strong> button, you'll be redirected to the project configuration page. Your project page will look like the below. Just fill in all the details:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/fillinfoindeno.png" alt="Fill new project configuration and link github" width="600" height="400" loading="lazy">
<em>Fill out your new project configuration and link to GitHub</em></p>
<p>The first time click the GitHub button. After that, Deno deploys and asks for permission for a GitHub account. After granting all the permissions, your project page looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/clicktolinkbuttontodeployblogondeno.png" alt="Image" width="600" height="400" loading="lazy">
<em>After finishing the information</em></p>
<ol>
<li>After opening the project page, you fill in all the required information.</li>
<li>Then, in the second step, select the GitHub repository.</li>
<li>After selecting the GitHub repository, select a branch, then choose  <code>main.tsx</code> file.</li>
<li>Give any project name, but make sure you name is in lowercase letters – for example, my-new-website. Otherwise, you'll get a capital case error.</li>
<li>Click on the environment variable and add an environment (if you have one - otherwise, skip it).</li>
</ol>
<p>And that's it - you've done all the configuration successfully. Now click on the link button.</p>
<h3 id="heading-deployment-is-finished">Deployment is finished</h3>
<p>After deployment is finished, you'll see the website dashboard. Click on the view button to view your production-ready website.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/denowebsitedashborad.png" alt="Your website dashboard with deno looks like after successfully website deployment" width="600" height="400" loading="lazy">
<em>Your website dashboard with deno looks like this after successful website deployment.</em></p>
<p>Here's a tip to help you manage all your markdown files and speed up your written work. The VS Code code editor has a free, open-source <a target="_blank" href="https://frontmatter.codes/">FrontMatter VS Code extension</a>. It's a great tool to manage all your markdown files inside VS Code with the FrontMatter dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/Screenshot-from-2022-09-13-18-18-32.png" alt="manage markdown file with vscode extenshion" width="600" height="400" loading="lazy">
<em>Manage markdown files with VS Code extension</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Deno blog module is an excellent library for creating a personal blog in five minutes and deploying it with Deno. Deno deployment is speedy. It takes less than ten seconds. </p>
<p>I think the Deno blog module is best for personal use because you do not need to customize many things. You only customize the header, footer, and various sections.</p>
<p>Thank you for reading!</p>
<h3 id="heading-references-to-help-you-setup-your-blog">References to help you setup your blog:</h3>
<ul>
<li><a target="_blank" href="https://deno.land/">https://deno.land/</a></li>
<li><a target="_blank" href="https://deno.com/deploy">https://deno.com/deploy</a></li>
<li><a target="_blank" href="https://deno.land/x/blog@0.5.0">https://deno.land/x/blog</a></li>
<li><a target="_blank" href="https://deno.land/x/dotenv">https://deno.land/x/dotenv</a></li>
<li><a target="_blank" href="https://frontmatter.codes/">https://frontmatter.codes/</a></li>
</ul>
<hr>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use GitHub Discussions as Your Blog's Chat System ]]>
                </title>
                <description>
                    <![CDATA[ By Rakesh Potnuru If you have a personal blog, you might be using a comment system to manage your blog's discussions and comments. If so, it's time to think about updating your comment system.  You can take your blog's comments to the next level with... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/github-discussions-as-chat-system/</link>
                <guid isPermaLink="false">66d460c78812486a37369d42</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 13 May 2022 15:48:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/05/GitHub-Discussions-as-chat-system-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Rakesh Potnuru</p>
<p>If you have a personal blog, you might be using a comment system to manage your blog's discussions and comments. If so, it's time to think about updating your comment system. </p>
<p>You can take your blog's comments to the next level with the help of GitHub Discussions. In this article, I am going to explain how to integrate GitHub discussions into your blog and make most out of it.</p>
<h2 id="heading-what-is-github-discussions">What is "GitHub Discussions"?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/screely-1652334880222-1.png" alt="GitHub discussions page" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/facebook/create-react-app/discussions">GitHub Discussions</a></em></p>
<p><a target="_blank" href="https://docs.github.com/en/discussions">GitHub discussions</a> is a forum that can be enabled on every GitHub repository. It makes it easy for developers to discuss new features, get feedback from the community, create polls, make announcements, and more. </p>
<p>GitHub Discussions is a one stop collaboration place for developers and community members. </p>
<h2 id="heading-how-to-use-github-discussions-as-chat-system">How to Use GitHub Discussions as Chat System</h2>
<p>To integrate GitHub Discussions into your blog, we are going to use <strong><a target="_blank" href="https://giscus.app/">giscus</a></strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/screely-1652335682833.png" alt="giscus website" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://giscus.app/">giscus</a></em></p>
<p>giscus is a GitHub Discussions-powered comment system. It lets you integrate discussions from your repository into your blog. </p>
<p>Your readers can leave comments on your blog, which will appear on both the blog and the discussions page of your repo.</p>
<h3 id="heading-advantages-of-using-discussions-as-your-blog-chat-system">Advantages of using Discussions as your blog chat system</h3>
<ul>
<li>It's completely free</li>
<li>There are no ads or tracking</li>
<li>It's super powerful</li>
<li>You have complete control over comments and full moderation authority.</li>
<li>There are lots of themes</li>
<li>It's quite customizable</li>
<li>You can self-host on your own servers</li>
</ul>
<p>Just keep in mind that this tool is mainly suited for dev blogs because mostly developers use GitHub.</p>
<h2 id="heading-how-to-integrate-giscus-in-your-blog">How to Integrate giscus in Your Blog</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li>A blog (you must have access to the source code)</li>
<li>A <a target="_blank" href="https://github.com/">GitHub account</a></li>
<li>Your selected repository must be public</li>
</ul>
<p>First, you'll need to enable discussions for your repo.</p>
<p>Go to the repo <strong>Settings</strong> -&gt; Under the <strong>Features</strong> section -&gt; Check the Discussions box.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-123606.png" alt="Discussions enabled" width="600" height="400" loading="lazy">
<em>Discussions enabled</em></p>
<p>Next, install the giscus app in your repo.</p>
<p>Go to <a target="_blank" href="https://github.com/apps/giscus">https://github.com/apps/giscus</a>, follow the prompts, and give access to only the selected repo.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-124524.png" alt="selecting repository access" width="600" height="400" loading="lazy">
<em>Repository access</em></p>
<p>Now the important part: we need to configure the giscus widget.</p>
<p>First, go to giscus <a target="_blank" href="https://giscus.app/">homepage</a> and scroll to <strong>Configuration</strong> section.</p>
<p>Select your widget language. This is the language in which you want to display your widget.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-125354.png" alt="Selecting language" width="600" height="400" loading="lazy">
<em>Selecting language</em></p>
<p>Then type your <strong>repo name</strong> along with your <strong>username</strong> like <code>username/reponame</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-125933.png" alt="Typing repository name along with username" width="600" height="400" loading="lazy">
<em>Typing repository name along with username</em></p>
<p>For <strong>Page ↔️ Discussions Mapping</strong>, I would recommended choosing "Discussion title contains page <code>URL</code>". But depending upon your needs choose the one that best suits you.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-130653.png" alt="Page ↔️ Discussions Mapping" width="600" height="400" loading="lazy">
<em>Page ↔️ Discussions Mapping</em></p>
<p>Next, create a category in your discussions page on your GitHub repo – something like "Comments" – or choose existing category.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-131045.png" alt="Creating a discussion category" width="600" height="400" loading="lazy">
<em>Creating a discussion category</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-131542.png" alt="Choosing category" width="600" height="400" loading="lazy">
<em>Choosing a category</em></p>
<p>Then enable the optional <strong>Features</strong> if you want.</p>
<p>Next, select the theme. And don't worry – you can programmatically toggle different themes.</p>
<p>Finally, copy and paste in the generated code.</p>
<p>Giscus will generate a script tag based on your settings, which you can paste into your code. But we will see how to use the giscus component.</p>
<h3 id="heading-how-to-use-giscus-component">How to use giscus-component</h3>
<p>If your blog is built with React/Vue/Svelte or Web components then you can install the giscus component.</p>
<p>For example, to integrate giscus in React, do the following:</p>
<ul>
<li>Install the giscus package.</li>
</ul>
<pre><code class="lang-bash">npm i @giscus/react

or

yarn add <span class="hljs-variable">$giscus</span>/react
</code></pre>
<ul>
<li>Then import <code>giscus</code> in your component and use it. Copy the attributes which we got in the last step, remove <code>data-</code> from all the attributes, and covert attributes into valid <code>jsx</code> attributes.</li>
</ul>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Giscus <span class="hljs-keyword">from</span> <span class="hljs-string">'@giscus/react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Giscus</span>
      <span class="hljs-attr">id</span>=<span class="hljs-string">"comments"</span>
      <span class="hljs-attr">repo</span>=<span class="hljs-string">"giscus/giscus-component"</span>
      <span class="hljs-attr">repoId</span>=<span class="hljs-string">"MDEwOlJlcG9zaXRvcnkzOTEzMTMwMjA="</span>
      <span class="hljs-attr">category</span>=<span class="hljs-string">"Announcements"</span>
      <span class="hljs-attr">categoryId</span>=<span class="hljs-string">"DIC_kwDOF1L2fM4B-hVS"</span>
      <span class="hljs-attr">mapping</span>=<span class="hljs-string">"specific"</span>
      <span class="hljs-attr">term</span>=<span class="hljs-string">"Welcome to @giscus/react component!"</span>
      <span class="hljs-attr">reactionsEnabled</span>=<span class="hljs-string">"1"</span>
      <span class="hljs-attr">emitMetadata</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">inputPosition</span>=<span class="hljs-string">"top"</span>
      <span class="hljs-attr">theme</span>=<span class="hljs-string">"light"</span>
      <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>
      <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>
    /&gt;</span></span>
  );
}
</code></pre>
<p>It's basically the same procedure for other frameworks.</p>
<h2 id="heading-heres-the-final-result">Here's the Final Result</h2>
<p>This is how the whole thing works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/ezgif-4-4662469443-2.gif" alt="Final result" width="600" height="400" loading="lazy">
<em>Final result</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we learnt the benefits of using GitHub discussions as a chat system. We also learned how to create and integrate the giscus widget in our website.</p>
<p>I hope you found this helpful. Make sure to ⭐ the giscus <a target="_blank" href="https://github.com/giscus">GitHub repo</a> to support them.</p>
<p>Follow me on <a target="_blank" href="https://twitter.com/rakesh_at_tweet">Twitter</a> where I share random tips, resources, and my learnings around web development and technical writing.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own Blog with Next.js and MDX ]]>
                </title>
                <description>
                    <![CDATA[ By Caleb Olojo When I decided to build my blog, I found many tools out there that were readily available. I looked at Gastby along with content management systems like Ghost, Contentful, Sanity dot io, and HUGO.  But I needed something that I could h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-own-blog-with-next-js-and-mdx/</link>
                <guid isPermaLink="false">66d45ddbc7632f8bfbf1e3ed</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mdx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 21 Apr 2022 02:28:29 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/thoughts.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Caleb Olojo</p>
<p>When I decided to build my blog, I found many tools out there that were readily available. I looked at <a target="_blank" href="https://www.gatsbyjs.com/">Gastby</a> along with content management systems like <a target="_blank" href="https://ghost.org/">Ghost</a>, <a target="_blank" href="https://www.contentful.com/">Contentful</a>, <a target="_blank" href="https://www.sanity.io/">Sanity dot io</a>, and <a target="_blank" href="https://gohugo.io/">HUGO</a>. </p>
<p>But I needed something that I could have total control over. I've also always been someone who loves the flexibility of writing own my custom code. When I do this, I can conveniently go back to where any issues might be when a problem arises.</p>
<p>Gatsby provides this flexibility, and it is something I could get familiar with pretty easily since it's built on a library I use every day (React.js). But, I found out that I can do the exact same thing with <a target="_blank" href="https://nextjs.org">Next.js</a> by integrating <a target="_blank" href="https://mdxjs.com/">MDX</a>. </p>
<p><em>"What is MDX?"</em> You might ask me.</p>
<p>Well... MDX is more or less like the markdown files we always see in GitHub repositories. MDX brings this flexibility into a markdown file by allowing you to literally write or import JavaScript (React) components into your articles. This in turn saves you from writing repetitive code.</p>
<p>In this article, I am going to show you how I built my blog with these tools, so you can also try building something similar. You'll like this simple stack if you are a person who loves the flexibility that this approach brings. </p>
<p>So, sit tight, and let's get started.</p>
<h2 id="heading-how-to-start-building-my-trial-and-error">How to Start Building – My Trial and Error</h2>
<p>To build a blog with Next.js and MDX, there are four popular options that you can choose from.</p>
<p>They are:</p>
<ul>
<li><a target="_blank" href="https://www.npmjs.com/package/@next/mdx">@next/mdx</a>, which is the official tool built by the Next.js team</li>
<li>Kent C. Dodds' <a target="_blank" href="https://github.com/kentcdodds/mdx-bundler">mdx-bundler</a></li>
<li><a target="_blank" href="https://github.com/hashicorp/next-mdx-remote">next-mdx-remote</a>, which is a tool built by the Hashicorp team</li>
<li><a target="_blank" href="https://github.com/hashicorp/next-mdx-enhanced">next-mdx-enhanced</a>, which is a tool also built by Hashicorp (I honestly don't know why they decided to build two)</li>
</ul>
<p>At first, I started by using Kent's mdx-bundler, but then I ran into a lot of problems with the tool. It is a library that is based on the new ECMAScript standards that allow us to create ESModules in the browser, and I was using a very old version of Next.js (V10.1.3, my bad honestly, I didn't know any better).</p>
<p>I did a lot of downgrading and upgrading of Next.js to fix this problem to no avail. There was a certain error that stuck with me, and refused to go away for days. <em>Yes, for days!</em> I felt like crying during that period. Take a look at the error below:</p>
<blockquote>
<p>module not found: can't resolve 'builtin-modules'</p>
</blockquote>
<p>Apparently, for mdx-bundler to work, it needs another npm package called esbuild to do the necessary compiling processes that work under the hood.</p>
<pre><code class="lang-bash">npm i mdx-bundler esbuild
</code></pre>
<p>Luckily for me — at least I thought I was lucky — <a target="_blank" href="https://github.com/kentcdodds/mdx-bundler/issues/18">Cody Brunner submitted an issue about this particular error</a>. Going through the discussions on the issue, a lot of possible fixes were suggested, some of them were related to Webpack, modifying your <code>next.config.js</code> file, and whatnot.</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">future</span>: {
    <span class="hljs-comment">// Opt-in to webpack@5</span>
    <span class="hljs-attr">webpack5</span>: <span class="hljs-literal">true</span>,
  },
  <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">webpack</span>: <span class="hljs-function">(<span class="hljs-params">config, { buildId, dev, isServer, defaultLoaders, webpack }</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!isServer) {
      <span class="hljs-comment">// https://github.com/vercel/next.js/issues/7755</span>
      config.resolve = {
        ...config.resolve,
        <span class="hljs-attr">fallback</span>: {
          ...config.resolve.fallback,
          <span class="hljs-attr">child_process</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">fs</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-string">'builtin-modules'</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">worker_threads</span>: <span class="hljs-literal">false</span>,
        },
      }
    }

    <span class="hljs-keyword">return</span> config
  },
}
</code></pre>
<p>In the snippet above, it shows that Webpack5 was still a feature that was in progress for Next.js – hence the snippet below in the config:</p>
<pre><code class="lang-js">future: {
  <span class="hljs-attr">webpack5</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<p>But, now the latest version of Next.js supports Webpack5 by default, so there's no need to add that object — if it works for you — in the config. </p>
<p>After going through the discussions, I found a comment (by Kent) that says running <code>npm update</code> would fix the issue, and it did work for Cody Brunner. But not for me apparently.</p>
<p>When I couldn't find a possible fix to this error, I decided to use next-mdx-remote, and the only issue I faced was the breaking change that was added to the tool. Before version 3 of next-mdx-remote you would normally render parsed markdown content by doing the following:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> renderToString <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote/render-to-string'</span>
<span class="hljs-keyword">import</span> hydrate <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote/hydrate'</span>
<span class="hljs-keyword">import</span> Test <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/test'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TestPage</span>(<span class="hljs-params">{ source }</span>) </span>{
  <span class="hljs-keyword">const</span> content = hydrate(source, { components })

  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"content"</span>&gt;</span>{content}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// MDX text - can be from a local file, database, anywhere</span>
  <span class="hljs-keyword">const</span> source = <span class="hljs-string">'Some **mdx** text, with a component &lt;Test /&gt;'</span>
  <span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> renderToString(source, { components })

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">source</span>: mdxSource,
    },
  }
}
</code></pre>
<p>The breaking change that was added in version 3 of the package stripped off a lot of internal code that was perceived to cause poor experiences for people who were using it at that time.</p>
<p>The team went on to announce the reason behind this change and the major changes. Take a look at them below.</p>
<blockquote>
<p>This release includes a full rewrite of the internals of next-mdx-remote to make it faster, lighter-weight, and behave more predictably! The migration should be fairly quick for most use-cases, but it will require some manual changes. Thanks to our community for testing out this release and providing early feedback. heart.</p>
</blockquote>
<h4 id="heading-major-changes-to-next-mdx-remote">Major changes to next-mdx-remote:</h4>
<ul>
<li><code>renderToString</code> has been replaced with <code>serialize</code></li>
<li><code>hydrate</code> has been replaced with <code>&lt;MDXRemote /&gt;</code></li>
<li>Removed provider configuration. React context usage should now work<br>without additional effort.</li>
<li>Content will now hydrate immediately by default</li>
<li>Dropped support for IE11 by default</li>
</ul>
<p>With this new change, the previous implementation will now become:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote/serialize'</span>
<span class="hljs-keyword">import</span> { MDXRemote } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote'</span>

<span class="hljs-keyword">import</span> { Test, Image, CodeBlock } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/'</span>

<span class="hljs-keyword">const</span> components = { Test }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TestPage</span>(<span class="hljs-params">{ source }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"content"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">MDXRemote</span> {<span class="hljs-attr">...source</span>} <span class="hljs-attr">components</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">Test</span>, <span class="hljs-attr">Image</span>, <span class="hljs-attr">CodeBlock</span> }} /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// MDX text - can be from a local file, database, anywhere</span>
  <span class="hljs-keyword">const</span> source = <span class="hljs-string">'Some **mdx** text, with a component &lt;Test /&gt;'</span>
  <span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> serialize(source)

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">source</span>: mdxSource,
    },
  }
}
</code></pre>
<h2 id="heading-how-to-build-the-blog">How to Build the Blog</h2>
<p>In the previous section, I walked you through some of the issues I encountered while I was choosing a suitable tool to use. </p>
<p>In this section, we're going to cover how you can build a similar blog like mine.</p>
<p>We'll start by creating a Next.js app with the command below:</p>
<pre><code class="lang-bash">npx create-next-app blog
</code></pre>
<p>The command above will give you a boilerplate of a typical Next.js app. For the sake of brevity, I'll be focusing more on the <code>pages</code> and <code>src/utils</code> folders of this app.</p>
<pre><code class="lang-bash">|--pages
|   |-- blog
|   |   |-- index.js
|   |   |-- [slug].js
|   |-- _app.js
|   |-- index.js
|--src
|   |-- utils
|        |-- mdx.js
|--data
|   |-- articles
|        |-- example-post.mdx
|        |-- example-post2.mdx
</code></pre>
<p>In a typical blog, we'd need to write blog posts or articles. In this blog, we're using markdown (MDX) to write our articles, which is why you can see that we have two <code>.mdx</code> files inside the <code>data/articles</code> directory. You can have more than that, as far as the number of articles you want to write goes.</p>
<h2 id="heading-how-to-read-the-markdown-mdx-files">How to Read the Markdown (MDX) Files</h2>
<p>In this section, we're going to start by writing some reusable functions inside <code>src/utils/mdx.js</code>. </p>
<p>The functions we're writing here will be using Node.js' FileSystem API. We'll be calling the functions at the server-side in the pages folder because Next.js has some <a target="_blank" href="https://nextjs.org/docs/basic-features/data-fetching/overview">data-fetching methods</a> that runs on the server.</p>
<p>Let's start by installing the dependencies that we need for now. As we progress, we'll be adding other dependencies:</p>
<pre><code class="lang-bash">npm install gray-matter reading-time next-mdx-remote glob dayjs
</code></pre>
<p>The command above will get all packages listed above as dependencies in our blog project.</p>
<p><code>gray-matter</code> will parse the content in the <code>.mdx</code> files to readable HTML content.</p>
<p><code>reading-time</code> assigns an approximate time to read a blog post or article based on the word count.</p>
<p><code>next-mdx-remote</code> does the background compilation of the MDX files by allowing them to be loaded within Next.js' <code>getStaticProps</code> or <code>getServerSideProps</code> data-fetching method, and hydrated properly on the client.</p>
<p><code>glob</code> gives us access to match the file patterns in <code>data/articles</code>, which we'll be using as the slug of the article.</p>
<p><code>dayjs</code> is a JavaScript library that helps to parse, manipulate, validate, and display dates that we would be adding to the metadata of each article.</p>
<p>We've seen the basic functions of the packages we installed. Now let's start writing the functions that'll read the files in the articles directory.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>
<span class="hljs-keyword">import</span> matter <span class="hljs-keyword">from</span> <span class="hljs-string">'gray-matter'</span>
<span class="hljs-keyword">import</span> readingTime <span class="hljs-keyword">from</span> <span class="hljs-string">'reading-time'</span>
<span class="hljs-keyword">import</span> { sync } <span class="hljs-keyword">from</span> <span class="hljs-string">'glob'</span>

<span class="hljs-keyword">const</span> articlesPath = path.join(process.cwd(), <span class="hljs-string">'data/articles'</span>)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSlug</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> paths = sync(<span class="hljs-string">`<span class="hljs-subst">${articlesPath}</span>/*.mdx`</span>)

  <span class="hljs-keyword">return</span> paths.map(<span class="hljs-function">(<span class="hljs-params">path</span>) =&gt;</span> {
    <span class="hljs-comment">// holds the paths to the directory of the article</span>
    <span class="hljs-keyword">const</span> pathContent = path.split(<span class="hljs-string">'/'</span>)
    <span class="hljs-keyword">const</span> fileName = pathContent[pathContent.length - <span class="hljs-number">1</span>]
    <span class="hljs-keyword">const</span> [slug, _extension] = fileName.split(<span class="hljs-string">'.'</span>)

    <span class="hljs-keyword">return</span> slug
  })
}
</code></pre>
<p>In the snippet above, we've imported the Node.js FileSystem from its module and the other packages. The first variable declaration, <code>articlesPath</code>, holds the path to where all the articles can be found.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> articlesPath = path.join(process.cwd(), <span class="hljs-string">'data/articles'</span>)
</code></pre>
<p>We're using the <code>path</code> module to get access to where the articles are by tapping into the <code>process</code> API of Node.js which gives us direct access to the <code>cwd()</code> (Current Working Directory) object.</p>
<p>The <code>getSlug</code> function will get a unique article when it's clicked by the user<br>on the blog page. You'll see that we're referencing the <code>articlesPath</code> variable that was declared before, and we're passing it to the <code>sync</code> function of the <code>glob</code> package. This will in turn match any file that has the <code>.mdx</code> extension, and give us an array with a list of these files.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> paths = sync(<span class="hljs-string">`<span class="hljs-subst">${articlesPath}</span>/*.mdx`</span>)
</code></pre>
<p>With that being said, we'll return an array of modified file names. The <code>pathContent</code> variable holds the path to all the articles in the articles directory, so we're using JavaScript to remove all the "forward-slashes" with the <code>split()</code> method of JavaScript.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> fileName = pathContent[pathContent.length - <span class="hljs-number">1</span>]
<span class="hljs-keyword">const</span> [slug, _extension] = fileName.split(<span class="hljs-string">'.'</span>)
</code></pre>
<p>The <code>fileName</code> variable declaration gets the last part of path, say for example <code>"/data/articles/example-post.mdx"</code>, since it is an array, and returns the last part which is <code>/example-post.mdx</code>. The next variable goes on to remove the period <code>(.)</code> from the filename itself, so we'll be left with <code>example-post</code> as the slug.</p>
<h2 id="heading-how-to-parse-article-content-from-the-slug">How to Parse Article Content from the Slug</h2>
<p>The next function gets and parses the content in our MDX files from the slugs. It returns an object of metadata that we'll be using as we progress.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getArticleFromSlug</span>(<span class="hljs-params">slug</span>) </span>{
  <span class="hljs-keyword">const</span> articleDir = path.join(articlesPath, <span class="hljs-string">`<span class="hljs-subst">${slug}</span>.mdx`</span>)
  <span class="hljs-keyword">const</span> source = fs.readFileSync(articleDir)
  <span class="hljs-keyword">const</span> { content, data } = matter(source)

  <span class="hljs-keyword">return</span> {
    content,
    <span class="hljs-attr">frontmatter</span>: {
      slug,
      <span class="hljs-attr">excerpt</span>: data.excerpt,
      <span class="hljs-attr">title</span>: data.title,
      <span class="hljs-attr">publishedAt</span>: data.publishedAt,
      <span class="hljs-attr">readingTime</span>: readingTime(source).text,
      ...data,
    },
  }
}
</code></pre>
<p>In the snippet above, we're using Node.js' <code>readFileSync</code> function from the FileSystem API to read the files in the <code>articleDir</code> in a synchronous manner. </p>
<p>What we're doing with this function — <code>readFileSync</code> — is telling Node to stop other processes that are currently going on, and perform this operation for us.</p>
<p>You can learn more about it <a target="_blank" href="https://www.geeksforgeeks.org/node-js-fs-readfilesync-method/">here</a> if you want to.</p>
<p>If you go ahead and <code>console.log(source)</code> in your terminal, you'll get a <code>&lt;Buffer&gt;</code> — which isn't readable — data type in your console. </p>
<p>This is where the <code>gray-matter</code> package comes to save the day. It helps in parsing the markdown content in the source to something — readable HTML — that you and I can understand.</p>
<p>Here, we're destructuring <code>content</code> and <code>data</code> variables, assigning it to the <code>matter</code> package (which parses the source) and returns an object that holds our <code>content</code> and <code>frontmatter: data</code> variables:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { content, data } = matter(source)

<span class="hljs-keyword">return</span> {
  content,
  <span class="hljs-attr">frontmatter</span>: {
    slug,
    <span class="hljs-attr">excerpt</span>: data.excerpt,
    <span class="hljs-attr">title</span>: data.title,
    <span class="hljs-attr">publishedAt</span>: data.publishedAt,
    <span class="hljs-attr">readingTime</span>: readingTime(source).text,
    ...data,
  },
}
</code></pre>
<p>We need a way to display all the articles on the blog page. The function below does that for us, by utilizing the <code>reduce()</code> method of JavaScript to return an array of all the articles in the articles directory.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllArticles</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> articles = fs.readdirSync(path.join(process.cwd(), <span class="hljs-string">'data/articles'</span>))

  <span class="hljs-keyword">return</span> articles.reduce(<span class="hljs-function">(<span class="hljs-params">allArticles, articleSlug</span>) =&gt;</span> {
    <span class="hljs-comment">// get parsed data from mdx files in the "articles" dir</span>
    <span class="hljs-keyword">const</span> source = fs.readFileSync(
      path.join(process.cwd(), <span class="hljs-string">'data/articles'</span>, articleSlug),
      <span class="hljs-string">'utf-8'</span>
    )
    <span class="hljs-keyword">const</span> { data } = matter(source)

    <span class="hljs-keyword">return</span> [
      {
        ...data,
        <span class="hljs-attr">slug</span>: articleSlug.replace(<span class="hljs-string">'.mdx'</span>, <span class="hljs-string">''</span>),
        <span class="hljs-attr">readingTime</span>: readingTime(source).text,
      },
      ...allArticles,
    ]
  }, [])
}
</code></pre>
<p>You can see how we're using <code>readdirSync()</code> to synchronously read all the files inside <code>data/articles</code>. The <code>source</code> variable can be accessed by reading all the files with their respective slugs and getting their content parsed with the <code>gray-matter</code> package.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> source = fs.readFileSync(
  path.join(process.cwd(), <span class="hljs-string">'data/articles'</span>, articleSlug),
  <span class="hljs-string">'utf-8'</span>
)
<span class="hljs-keyword">const</span> { data } = matter(source)
</code></pre>
<p>If you take a look at the snippet below, you'll see how we're using the <code>reading-time</code> package to get the approximate time it will take to read this article. We get the slug that will be attached to this article by stripping the last part of the article — <code>blog/example-post.mdx</code> — and replacing it with an empty string. This makes it accessible via "blog/example-post".</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">slug</span>: articleSlug.replace(<span class="hljs-string">'.mdx'</span>, <span class="hljs-string">''</span>),
  <span class="hljs-attr">readingTime</span>: readingTime(source).text,
}
</code></pre>
<p>The <code>readingTime</code> has some methods that you can assign to it, one of them is the <code>text</code> method. You can try removing this value, saving your code, and allowing Next.js to throw an error, so you can get a glimpse of the values that you can use.</p>
<h2 id="heading-how-to-display-a-list-of-articles">How to Display a List of Articles</h2>
<p>In the previous sections, we have seen how we can use the Node.js FileSystem API and a couple of other tools to get access to where all our articles are. </p>
<p>In this section, we'll be displaying the articles on a webpage.</p>
<p>We'll start with the <code>index</code> file in the blog folder. In this file, we'll be using the data-fetching method — <code>getStaticProps</code> — to render the articles on the page.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { getAllArticles } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../src/utils/mdx'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> articles = <span class="hljs-keyword">await</span> getAllArticles()

  articles
    .map(<span class="hljs-function">(<span class="hljs-params">article</span>) =&gt;</span> article.data)
    .sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (a.data.publishedAt &gt; b.data.publishedAt) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
      <span class="hljs-keyword">if</span> (a.data.publishedAt &lt; b.data.publishedAt) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>

      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
    })

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">posts</span>: articles.reverse(),
    },
  }
}
</code></pre>
<p>In the snippet above, we imported the <code>getAllArticles</code> function and used it in the data-fetching method of Next.js. </p>
<p>You'll notice how we're sorting the articles based on the date they were published. We'll eventually map the list of articles that will be returned as props to the index (blog) page.</p>
<pre><code class="lang-js">articles
  .map(<span class="hljs-function">(<span class="hljs-params">article</span>) =&gt;</span> article.data)
  .sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (a.data.publishedAt &gt; b.data.publishedAt) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">if</span> (a.data.publishedAt &lt; b.data.publishedAt) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
  })
</code></pre>
<p>Lest I forget, this is how the content of your typical article file will look in markdown syntax below:</p>
<pre><code class="lang-md">---
title: 'Next.js Image optimization error on Netlify'
publishedAt: '2022-04-16'
excerpt: 'Next.js has a built-in Image component that comes with a lot of performance optimization features when you are using it.'
<span class="hljs-section">cover<span class="hljs-emphasis">_image: 'path/to/where/image/is/stored'
---

rest of the content falls here</span></span>
</code></pre>
<p>You may ask me, <em>"why do we need to sort the articles by date if we can just use the <code>reverse()</code> method to re-order the array of articles?"</em>. </p>
<p>I think it is appropriate for us to sort the list of articles by comparing them with the date they were published and still apply the <code>reverse</code> method to the array.</p>
<p>Say, for example, we forget to add the published dates to the articles. Then the <code>reverse()</code> method will just perform the operation on the array without comparing the dates in a LIFO — Last-In-First-Out — pattern, if the sort function is missing. So it is better to sort the articles and still reverse the content of the array.</p>
<p>Now that we've returned the list of articles as props we can go ahead to map them onto the page.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">'next/head'</span>
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>
<span class="hljs-keyword">import</span> { getAllArticles } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../src/utils/mdx'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">BlogPage</span>(<span class="hljs-params">{ posts }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.Fragment</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>My Blog<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {posts.map((frontMatter) =&gt; {
          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">blog</span>/${<span class="hljs-attr">frontMatter.slug</span>}`} <span class="hljs-attr">passHref</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"title"</span>&gt;</span>{frontMatter.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"summary"</span>&gt;</span>{frontMatter.excerpt}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"date"</span>&gt;</span>
                  {dayjs(frontMatter.publishedAt).format('MMMM D, YYYY')} <span class="hljs-symbol">&amp;mdash;</span>{' '}
                  {frontMatter.readingTime}
                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          )
        })}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">React.Fragment</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  ...
}
</code></pre>
<p>In the snippet above, we're using the <code>Link</code> component to route the user to a dynamic page with the unique article's slug. This is the reason we created a file called <code>[slug].js</code>, if you can recall. It is a dynamic route, and you can read more about it <a target="_blank" href="https://nextjs.org/docs/routing/dynamic-routes">here</a>.</p>
<h2 id="heading-how-to-display-a-unique-article">How to Display a Unique Article</h2>
<p>In the last section, we were able to render the list of articles onto the webpage. In this section, we'll be rendering a unique article that gets clicked on by the user in a new route.</p>
<p>We're also going to be using a tool called rehype to customize what our blog post will look like. Rehype is an HTML pre-processor that is powered by plugins. We'll be using some of these plugins in this section, so let's install them now.</p>
<pre><code class="lang-bash">npm i rehype-highlight rehype-autolink-headings rehype-code-titles rehype-slug
</code></pre>
<p><code>rehype-highlight</code> allows us to add syntax highlighting to our code blocks.</p>
<p><code>rehype-autolink-headings</code> is a plugin that adds links to headings from h1 to h6.</p>
<p><code>rehype-code-titles</code> adds language/file titles to your code.</p>
<p><code>rehype-slug</code> is a plugin that adds an <code>id</code> attributes to headings.</p>
<p>Now that we've seen the roles that each plugin carries out, let's start working on the <code>[slug].js</code> file. In this file, we'll be using two data-fetching methods of Next.js — <code>getStaticProps</code> and <code>getStaticPaths</code>.</p>
<p>We're using these two methods because we'll be fetching data (articles) that are unique to the path (slugs) that the user is redirected to.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// dynamically generate the slugs for each article(s)</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// getting all paths of each article as an array of</span>
  <span class="hljs-comment">// objects with their unique slugs</span>
  <span class="hljs-keyword">const</span> paths = (<span class="hljs-keyword">await</span> getSlug()).map(<span class="hljs-function">(<span class="hljs-params">slug</span>) =&gt;</span> ({ <span class="hljs-attr">params</span>: { slug } }))

  <span class="hljs-keyword">return</span> {
    paths,
    <span class="hljs-comment">// in situations where you try to access a path</span>
    <span class="hljs-comment">// that does not exist. it'll return a 404 page</span>
    <span class="hljs-attr">fallback</span>: <span class="hljs-literal">false</span>,
  }
}
</code></pre>
<p>When you take a look at the snippet above, you'll see that we're obtaining the list of <code>paths</code> from the articles, and mapping that list of items (paths) to an array. This can be accessed with the <code>params</code> variable in the <code>getStaticProps</code> data-fetching method.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { getArticleFromSlug } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../src/utils/mdx"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{ params }</span>) </span>{
  <span class="hljs-comment">//fetch the particular file based on the slug</span>
  <span class="hljs-keyword">const</span> { slug } = params
  <span class="hljs-keyword">const</span> { content, frontmatter } = <span class="hljs-keyword">await</span> getArticleFromSlug(slug)

  <span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> serialize(content, {
    <span class="hljs-attr">mdxOptions</span>: {
      <span class="hljs-attr">rehypePlugins</span>: [
        rehypeSlug,
        [
          rehypeAutolinkHeadings,
          {
            <span class="hljs-attr">properties</span>: { <span class="hljs-attr">className</span>: [<span class="hljs-string">'anchor'</span>] },
          },
          { <span class="hljs-attr">behaviour</span>: <span class="hljs-string">'wrap'</span> },
        ],
        rehypeHighlight,
        rehypeCodeTitles,
      ],
    },
  })

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">post</span>: {
        <span class="hljs-attr">source</span>: mdxSource,
        frontmatter,
      },
    },
  }
}<span class="hljs-string">`</span>
</code></pre>
<p>In the snippet above, we're destructuring <code>content</code> and <code>frontmatter</code> — which is the metadata of the article — and assigning it to the <code>getArticleFromSlug</code> function which receives the slug of the article as an argument.</p>
<p>We continued by serializing the content of the article with next-mdx-remote's <code>serialize()</code> function, and pass the necessary rehype plugins in the <code>mdxOptions</code> object:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> serialize(content, {
  <span class="hljs-attr">mdxOptions</span>: {
    <span class="hljs-attr">rehypePlugins</span>: [
      rehypeSlug,
      [
        rehypeAutolinkHeadings,
        {
          <span class="hljs-attr">properties</span>: { <span class="hljs-attr">className</span>: [<span class="hljs-string">'anchor'</span>] },
        },
        { <span class="hljs-attr">behaviour</span>: <span class="hljs-string">'wrap'</span> },
      ],
      rehypeHighlight,
      rehypeCodeTitles,
    ],
  },
})
</code></pre>
<p>To wrap it up, we return the <code>content</code> of the article and the <code>frontmatter</code> as props that'll be accessed by the slug component.</p>
<pre><code class="lang-js"><span class="hljs-keyword">return</span> {
  <span class="hljs-attr">props</span>: {
    <span class="hljs-attr">post</span>: {
      <span class="hljs-attr">source</span>: mdxSource,
      frontmatter,
    },
  },
}
</code></pre>
<p>The props that we returned in the previous snippets can be accessed via the component below. </p>
<p>You'll notice that the <code>&lt;MDXRemote /&gt;</code> component receives the <code>{...source}</code> and custom React component props that we can use in our MDX files. This eradicates the process of having to write repetitive code over and over.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> dayjs <span class="hljs-keyword">from</span> <span class="hljs-string">'dayjs'</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">'next/head'</span>
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>
<span class="hljs-keyword">import</span> rehypeSlug <span class="hljs-keyword">from</span> <span class="hljs-string">'rehype-slug'</span>
<span class="hljs-keyword">import</span> { MDXRemote } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote'</span>
<span class="hljs-keyword">import</span> rehypeHighlight <span class="hljs-keyword">from</span> <span class="hljs-string">'rehype-highlight'</span>
<span class="hljs-keyword">import</span> rehypeCodeTitles <span class="hljs-keyword">from</span> <span class="hljs-string">'rehype-code-titles'</span>
<span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-mdx-remote/serialize'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'highlight.js/styles/atom-one-dark-reasonable.css'</span>
<span class="hljs-keyword">import</span> rehypeAutolinkHeadings <span class="hljs-keyword">from</span> <span class="hljs-string">'rehype-autolink-headings'</span>
<span class="hljs-keyword">import</span> { getSlug, getArticleFromSlug } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../src/utils/mdx'</span>
<span class="hljs-keyword">import</span> { SectionTitle, Text } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../data/components/mdx-components'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Blog</span>(<span class="hljs-params">{ post: { source, frontmatter } }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.Fragment</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{frontmatter.title} | My blog<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"article-container"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"article-title"</span>&gt;</span>{frontmatter.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"publish-date"</span>&gt;</span>
          {dayjs(frontmatter.publishedAt).format('MMMM D, YYYY')} <span class="hljs-symbol">&amp;mdash;</span>{' '}
          {frontmatter.readingTime}
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"content"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">MDXRemote</span> {<span class="hljs-attr">...source</span>} <span class="hljs-attr">components</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">Image</span>, <span class="hljs-attr">SectionTitle</span>, <span class="hljs-attr">Text</span> }} /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">React.Fragment</span>&gt;</span></span>
  )
}
</code></pre>
<p>In the snippet above, you'll notice how we destructured the post props into <code>{ source, frontmatter }</code>. So instead of doing this, in the <code>&lt;MDXRemote&gt;</code> component below we can just spread the source variable directly as a prop.</p>
<pre><code class="lang-jsx">&lt;MDXRemote {...post.source} /&gt;
</code></pre>
<p>Notice how we're also dynamically rendering the title of the page with the title of the article instead of the normal title? This is gotten from the frontmatter.</p>
<pre><code class="lang-jsx">&lt;Head&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{frontmatter.title} | My blog<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span></span>
&lt;/Head&gt;
</code></pre>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Every developer loves having their fancy themes applied to their editors. So we're not going to leave this out in this blog. </p>
<p>I'm currently using the <code>"atom-one-dark-reasonable"</code> theme for my syntax highlighting. You can import it from the <code>"highlight.js"</code> library — since the <code>rehype-highlight</code> plugin uses it under the hood — like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">'highlight.js/styles/atom-one-dark-reasonable.css'</span>
</code></pre>
<p>There are a lot of other themes <a target="_blank" href="https://highlightjs.org/static/demo/">here</a>, so you can choose any that you're comfortable with.</p>
<p>You might have noticed while reading this article that there are some components like the one in the image below – and you may have been wondering how it was created.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/tooltip-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can decide to have a lot of custom MDX components that you can use in your articles. But, I decided to target any element that I want to style in this article by assigning a generic className to it. So whenever I want to use it, I just reference that style in the element.</p>
<p>SEO is one of the important things when it comes to building a blog, and luckily for us, Next.js already has that covered for us. You can take a look at this article that walks you through <a target="_blank" href="https://seven.hashnode.dev/seo-in-nextjs-apps">How to add SEO Meta tags in your Next.js apps</a> and <a target="_blank" href="https://meje.dev/blog/meta-tags-error-in-nextjs">How I fixed a meta tag pre-rendering error in Next.js</a></p>
<p>There is an important thing that you must not forget, and that is the <code>next.config.js</code> file. You need to make sure that it is properly set up so you can avoid one of the version compatibility errors of the latest version of React — v18.0.0 — with next-mdx-remote.</p>
<p>Although the Hashicorp team said they've fixed <a target="_blank" href="https://github.com/hashicorp/next-mdx-remote/pull/250">this</a> in their latest release, it didn't work for me. A way to bypass this error is to install next-mdx-remote as a legacy peer dependency, like so:</p>
<pre><code class="lang-bash">npm i next-mdx-remote --legacy-peer-deps
</code></pre>
<p>And make sure to have a <code>next.config</code> file that looks like what you're seeing below.</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,

  <span class="hljs-attr">images</span>: {
    <span class="hljs-attr">loader</span>: <span class="hljs-string">'akamai'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">''</span>,
  },

  <span class="hljs-attr">webpack</span>: <span class="hljs-function">(<span class="hljs-params">config</span>) =&gt;</span> {
    config.resolve.alias = {
      ...config.resolve.alias,
      <span class="hljs-string">'react/jsx-runtime.js'</span>: <span class="hljs-built_in">require</span>.resolve(<span class="hljs-string">'react/jsx-runtime'</span>),
    }

    config.resolve = {
      ...config.resolve,

      <span class="hljs-attr">fallback</span>: {
        ...config.resolve.fallback,
        <span class="hljs-attr">child_process</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">fs</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-comment">// 'builtin-modules': false,</span>
        <span class="hljs-comment">// worker_threads: false,</span>
      },
    }

    <span class="hljs-keyword">return</span> config
  },
}
</code></pre>
<p>The <code>resolve.alias</code> object in the config above helps as a workaround in fixing the error below</p>
<h4 id="heading-what-to-do-if-you-get-a-server-error">What to do if you get a server error</h4>
<p>Error: Package subpath "./jsx-runtime.js" is not defined by "exports" in "path-to-node_modules/react/package.json"</p>
<p>Sometimes you may also encounter an error that has to do with the "builtin" modules of Node.js while you're deploying your project. The <code>config.resolve</code> object with the <code>fallback</code> key helps in removing that error.</p>
<p>You'll notice that there's an <code>image</code> object in the config.</p>
<pre><code class="lang-js">  images: {
    <span class="hljs-attr">loader</span>: <span class="hljs-string">'akamai'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">''</span>,
  },
</code></pre>
<p>Its role is to ensure that the proper image optimization process is used during the build process. You can take a look at an article I wrote about how you can fix the <a target="_blank" href="https://meje.dev/blog/image-optimization-error-in-nextjs">Next.js image optimization error on Netlify</a></p>
<p>Thank you so much for reading this article. I hope you found it helpful.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Choose a CMS – WordPress vs Ghost vs Shopify ]]>
                </title>
                <description>
                    <![CDATA[ WordPress, Shopify, and Ghost are some of the most powerful and popular CMS platforms out there. And you might wonder why so many people use them. Well, it's because they're easy to use, cost-effective, and highly efficient. With WordPress, Shopify, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/chose-a-cms-wordpress-vs-shopify-vs-ghost/</link>
                <guid isPermaLink="false">66d45e3f3a8352b6c5a2aa32</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ghost ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shopify ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WordPress ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Edan Ben-Atar ]]>
                </dc:creator>
                <pubDate>Wed, 02 Feb 2022 17:05:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/01/WordPress-vs-Ghost-vs-Shopify.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>WordPress, Shopify, and Ghost are some of the most powerful and popular CMS platforms out there.</p>
<p>And you might wonder why so many people use them. Well, it's because they're easy to use, cost-effective, and highly efficient.</p>
<p>With WordPress, Shopify, and Ghost, you can create a website from scratch in a matter of minutes.</p>
<p>Tailored to your needs, these <a target="_blank" href="https://www.weblime.com/stories/top-website-builders">powerful website builders</a> can help you achieve anything from building an e-commerce website to starting your own blog or showcasing your portfolio.</p>
<p>It's no wonder why so many businesses choose these platforms. But which one should you choose?</p>
<p>We can't simply say that one is better than the other, because there's no single solution that will work in every situation.</p>
<p>So in this article, we'll cover the three platforms and look at their strengths and weaknesses so that you can figure out what will work best for your particular situation.</p>
<h2 id="heading-if-you-want-to-build-a-blog-from-scratch-use-ghost">If you Want to Build a Blog From Scratch – Use Ghost</h2>
<p>If you are a blogger who is looking for an open-source platform, <a target="_blank" href="https://ghost.org/">Ghost</a> might be the perfect choice for you.</p>
<p>The fact that it’s open-source enables you to alter the code of the platform, customize it, and make it more suitable to your writing needs.</p>
<p>And even if you’re not a coder, you can get help from other users who are willing to contribute to the community by fixing bugs or adding new features.</p>
<p>Tutorials on how to install Ghost on your own web server are available online. You can also choose to go with a hosted plan, which allows you to use themes created by other users instead of relying on the default theme.</p>
<p>Ghost is a platform that is based on Node.js, and it runs using Node.js servers. It comes with a rich plugin system, allowing you to customize your blog to suit your own needs.</p>
<p>Ghost lets you to use Markdown instead of HTML for writing. This makes it easy for you to write directly in the browser window.</p>
<p>If you don’t know about Markdown, it is a lightweight markup language that allows you to write in rich text but with simpler code, as opposed to HTML.</p>
<p>In fact, one of the benefits of Ghost over other platforms is that your posts are written in Markdown rather than HTML or text files, which results in much cleaner code.</p>
<h3 id="heading-you-can-use-ghost-as-a-headless-cms">You Can Use Ghost as a Headless CMS</h3>
<p>Ghost has a flexible architecture, which means it can be used as a headless CMS. In fact, it's one of the most popular headless content management systems in the JAMStack.</p>
<p>A <a target="_blank" href="https://www.freecodecamp.org/news/what-is-headless-cms-explained/">headless CMS</a> allows you to build a front-end website or application, such as a mobile app, with an API that calls the CMS for its data. You can deploy the API and your website or app to separate servers or environments without having to change anything within your content.</p>
<p>Since the API delivers data from the CMS, you will have full control over that data, including permissions, tags, and categories. If you need to make changes to your content, you will only have to make those changes on one end rather than both.</p>
<p>Headless CMSs are often used for building websites and applications with JavaScript frameworks such as AngularJS and React.js​. However, if you need to render a dynamic site with multiple pages using a single API call, this type of CMS can be beneficial as well.</p>
<p>To use a headless CMS, you will need to learn how it works and how to utilize it through its API. This may require additional development resources, depending on your skill level.</p>
<p>This approach is gaining popularity among startups and agencies who want more control over the display of content while still retaining the ability to make updates through a central interface.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/ghost-screenshot-admin.png" alt="Screenshot of Ghost admin interface" width="600" height="400" loading="lazy"></p>
<h3 id="heading-advantages-of-using-ghost">Advantages of Using Ghost</h3>
<p>Ghost is a free, easy-to-use CMS that doesn’t require any kind of coding skills.</p>
<p>Along with simplicity, Ghost CMS comes with lots of out-of-the-box features which make it really appealing to bloggers and small businesses.</p>
<p>You can add multiple authors, set up collaborative writing, track analytics, create polls, customer support forums, manage templates, and much more using drag and drop features.</p>
<p>Things get interesting as you start using third-party apps on Ghost. There are numerous integrations available for installing apps like Google Analytics and Disqus comments.</p>
<p>Some other pros include:</p>
<ul>
<li><p><strong>It has a clean, minimalistic design</strong>. If you want your blog to be beautiful, then Ghost CMS is great for that.</p>
</li>
<li><p><strong>Complete tools</strong>. You get all the tools that are required in creating a blog, starting from the installation to setting up the theme with the drag and drop features.</p>
</li>
<li><p><strong>Customizable themes</strong>. The themes can be customized using the built-in theme editor that makes it really easy for anyone to modify them as per their needs.</p>
</li>
<li><p><strong>Ghost community</strong>. If you’re stuck, you can find help from other Ghost users by going over their forums and asking questions.</p>
</li>
</ul>
<h2 id="heading-if-you-want-to-take-your-website-to-the-next-level-use-wordpress">If You Want to Take Your Website to the Next Level – Use WordPress</h2>
<p>You can use <a target="_blank" href="https://wordpress.org/">WordPress</a> as a simple blogging platform, but it is also a fully-featured content management system (CMS). It allows you to build custom websites and blogs if you want to take your business to the next level.</p>
<p>Because of its popularity, there is an active support community and lots of free training materials.</p>
<p>Tutorials for the basic functionality and more advanced features are widely available online.</p>
<p>WordPress is free, but if you need help installing it or customizing it for your needs, you can purchase web hosting from a reputable company. Hosting costs vary depending on the type of hosting service you choose.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-get-started-with-wordpress/">You can install WordPress yourself</a> or pay someone to do it for you. The WordPress website provides instructions on how to install the software on different server platforms.</p>
<p>Once installed on your server, WordPress will allow you to create your own blog page. You have complete control over the look and feel of your site through the use of themes, which are pre-built designs for the layout of your pages.</p>
<p>Themes are available for purchase or free download from the WordPress website. You can also <a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-create-your-own-wordpress-theme-from-scratch/">design your own custom theme</a> if you're up to the challenge.</p>
<p>You might want a more complicated site than what is provided by the basic WordPress installation. In that case, you can hire a designer to build you a customized theme or using an existing theme as a framework and add your own custom features through plugins. Plugins extend the features of themes with special design elements and programming functions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/WordPress-screen-themes.png" alt="Screenshot of WordPress themes in the admin UI" width="600" height="400" loading="lazy"></p>
<h3 id="heading-advantages-of-using-wordpress">Advantages of using WordPress</h3>
<p>WordPress is by far the most customizable option in this list. It features the most comprehensive library of themes and plugins, allows you to edit the source code quite easily, and even write your own custom functionalities.</p>
<p>You can think of WordPress as the CMS that falls between Ghost and Shopify, as it allows you to run an eCommerce store and a blog at the same time by simply installing a compatible theme.</p>
<p>WordPress lets you take your blog to the next level and scale your eCommerce store as your business grows.</p>
<p>The downside is that sooner or later you will need a developer’s help. This is because the more plugins you install, the slower your website will get, and users hate slow websites (not to mention search engines).</p>
<p>Some other pros include:</p>
<ul>
<li><p><strong>Extremely cheap to get started with</strong>. You can get a WordPress website up and running for free, and basic hosting plans start as low as $18.96 per year.</p>
</li>
<li><p><strong>Ability to assign user roles</strong>. At some point during your business growth cycle, you might need to bring in extra people to help you create, edit and publish content. WordPress comes with built-in user roles which will make your life a whole lot easier.</p>
</li>
<li><p><strong>Best marketing plugins on the market</strong>. You can install one of the many SEO plugins available in the plugin directory. Some examples include Yoast or Rank Math. These plugins help you optimize your content (like articles, pages, and even product pages) and increase your chances of outranking your competitors.</p>
</li>
<li><p><strong>A vast array of integrations</strong>. Chances are you will be using 3rd party SaaS products like Mailchimp or ActiveCampaign. Regardless of the service, you will find it extremely easy to integrate them with your website as almost all have created a WordPress plugin.</p>
</li>
<li><p><strong>The biggest online library of resources</strong>. The WordPress community is by far the largest which makes it easy to find answers to all your questions regarding your installation. There are tens of thousands of websites and forums focused solely on WordPress websites featuring tons of information.</p>
</li>
</ul>
<h2 id="heading-if-you-want-the-fastest-way-to-start-an-ecommerce-business-use-shopify">If You Want the Fastest Way to Start an eCommerce Business – Use Shopify</h2>
<p><a target="_blank" href="https://www.shopify.com/">Shopify</a> started in 2006 as an online store for snowboarding equipment. Back then, the CMS ecosystem was lacking a robust and easy-to-use solution, so the Shopify creators decided to code their own.</p>
<p>Needless to say, this was a hit. In the years that followed more and more stores have started to use the Shopify CMS. As of May 2021, more than 1.7 million businesses from some 175 countries use Shopify to run their businesses.</p>
<p>By far the simplest and fastest way to start an eCommerce business, Shopify is mainly used by nontechnical people that want to bring their brick and mortar stores online. In no more than a few minutes you can have your own website up and running and start accepting online payments.</p>
<p>But Shopify is also used by some of the biggest online stores on the Internet, which makes it a great option if you want to scale your business to the next level, and have the funds to do so.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Shopify-screenshot.png" alt="Screenshot of Shopify UI" width="600" height="400" loading="lazy"></p>
<p>Some of Shopify’s advantages:</p>
<ul>
<li><p><strong>Extremely easy to get started with</strong>. The CMS was built with ease of use in mind. Anyone can get a store up and running in no time. You don’t have to know how to code or even how to connect a domain to your website as Shopify does all that for you.</p>
</li>
<li><p><strong>Compatible with most major third-party services</strong>. Shopify connects out of the box with the biggest third-party services, like Klaviyo for email marketing or Facebook Ads for paid advertising.</p>
</li>
<li><p><strong>Plenty of apps in the app store</strong>. If you feel that your store needs additional functionality, like bundle discounts, for example, you will be happy to hear that there are tons of apps that can help you achieve this. Just be careful and look at their cost as they can go up rather quickly.</p>
</li>
</ul>
<p>Some of Shopify’s disadvantages:</p>
<ul>
<li><p><strong>Can get pretty expensive</strong>. Shopify is extremely easy to start with, but that comes with a cost. The cheapest plan (Basic) starts at $29 per month, while the most expensive one (Advanced) reaches $299 per month. The main difference is the commission applied by Shopify on payments.</p>
</li>
<li><p><strong>High payment fees</strong>. You have to take into account that Shopify charges up to a 2% fee on your transactions (on the Basic plan). This commission decreases with the plan to a minimum of 0.5% (on the Advance plan). In comparison, WooCommerce doesn’t charge you a percentage of your sales.</p>
</li>
<li><p><strong>Shopify developers are expensive</strong>. If you want to customize your store beyond basic functionalities you will need the help of a developer, and they are not cheap. However, there are a multitude of apps available that can help you achieve your goals.</p>
</li>
<li><p><strong>Most Shopify Apps have a monthly cost</strong>. You can find free apps in the app store, but most of them have a monthly cost (compared to a fixed one-time fee charged by the vast majority of WordPress plugins).</p>
</li>
<li><p><strong>The blog functionality isn't great</strong>. Shopify is an eCommerce-centric platform so there’s no wonder that they haven’t spent too much time optimizing their blog functionality. Big stores choose to host their blogs on WordPress while keeping their storefront on Shopify.</p>
</li>
</ul>
<p>In conclusion, you can use Ghost to start your blog almost instantly, or Shopify to configure a fast and secure online store. And, if you want to get the best out of both worlds, you might want to go with WordPress.</p>
<p><a target="_blank" href="https://www.weblime.com/stories/the-ultimate-wordpress-guide">The ultimate WordPress guide</a> is a free resource that will help you understand the basics and help you launch your new website in no time, so be sure to give it a read before you make the big decision of choosing a specific platform.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Start a Blog [Free 70-Page Book] ]]>
                </title>
                <description>
                    <![CDATA[ What follows is a full-length book on how to create a successful blog. I am a software developer and I've written 1,500 posts on my blog over the past few years. Millions of people have read my writing. And my blog is now one of the top 20,000 websit... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-start-a-blog-book/</link>
                <guid isPermaLink="false">66bb5a7eb6e566d0c2aea609</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Blogging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Flavio Copes ]]>
                </dc:creator>
                <pubDate>Mon, 11 Oct 2021 17:21:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/10/how-to-start-a-blog.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>What follows is a full-length book on how to create a successful blog.</p>
<p>I am a software developer and I've written 1,500 posts on my blog over the past few years. Millions of people have read my writing. And my blog is now one of the top 20,000 websites on earth (according to Amazon's Alexa website ranking tool).</p>
<p>In this book, we will not focus on the "how to install WordPress" aspect of blogging. We will also not discuss how to build your own blogging software. (That's a fun exercise, but not as important as what I'd like to discuss.)</p>
<p>This book will instead focus on developing the right mindset. It will also show you how to get systems in place so that you can run your blog over a long period of time.</p>
<p>The hard part of blogging is doing it sustainably. How do you blog in such a way that you can publish consistently? How do you become a household name in your field? How do you hold your audience's attention over time?</p>
<p>A lot of bloggers struggle with these challenges. There are so many blogs out there with 1 or 2 posts. Or the last post is from 2018 saying "Sorry I haven't published for a long time." With planning and execution, you can prevent this from happening to your blog.</p>
<p>Also, you may be wondering how relevant blogging is in the 2020s. After all, YouTube, TikTok, and Instagram get a lot of headlines in the news. Well I'm here to tell you that blogs are still powerful when it comes to content marketing and getting organic traffic from Google.</p>
<p>The main goal of the book (and yes, you can read the book in its entirety here) is to help you get started with blogging in the right way. So grab a beverage and a comfortable chair. And bookmark this page so you can come back and review, in case you can't finish it in one sitting.</p>
<p>And if you prefer, <a target="_blank" href="https://flaviocopes.com/page/book-blog/">you can download a PDF and ePub version of this <strong>How to Start a Blog</strong> book here</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-framing-your-blog-for-success">Framing your blog for success</a></li>
<li><a class="post-section-overview" href="#heading-the-benefits-of-having-a-successful-blog">The benefits of having a successful blog</a></li>
<li><a class="post-section-overview" href="#heading-pick-a-goal-for-your-blog">Pick a goal for your blog</a></li>
<li><a class="post-section-overview" href="#heading-own-your-platform">Own your platform</a></li>
<li><a class="post-section-overview" href="#heading-make-the-first-step">Make the first step</a></li>
<li><a class="post-section-overview" href="#heading-solutions-to-common-problems">Solutions to common problems</a></li>
<li><a class="post-section-overview" href="#heading-content-is-king">Content is king</a></li>
<li><a class="post-section-overview" href="#heading-practical-content-suggestions">Practical content suggestions</a></li>
<li><a class="post-section-overview" href="#heading-create-a-system">Create a system</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-people-to-your-blog">How to get people to your blog</a></li>
<li><a class="post-section-overview" href="#heading-expand-your-reach-with-an-email-list">Expand your reach with an email list</a></li>
<li><a class="post-section-overview" href="#heading-ups-and-downs">Ups and downs</a></li>
<li><a class="post-section-overview" href="#heading-making-money-with-a-blog">Making money with a blog</a></li>
<li><a class="post-section-overview" href="#heading-final-words">Final words</a></li>
</ol>
<h2 id="heading-framing-your-blog-for-success">Framing your blog for success</h2>
<p>People communicate and share information over the internet. We use it to look for help. Maybe we try to find the perfect recipe for dinner or the best book we should read this month.</p>
<p>A blog is a tiny website, made by one or a handful of people, with the goal of creating information that will help people.</p>
<p>On this blog, you will write posts. A post is a single page of the website containing some information.</p>
<p>The concept of a blog I'll talk about in this book is different from a diary, where you write about everyday things as they happen. That is something you can definitely do, but it's not what I'll be talking about here.</p>
<p>By blog, I mean a personal site, or a product/company site, where you regularly produce interesting content that can be beneficial to other people.</p>
<p>You want people to be able to find the solution to their problem on your blog.</p>
<p>I want to highlight this point because it's central. Your blog should be useful to other people. Without this fundamental concept, your blog has no reason to exist.</p>
<h3 id="heading-why-a-blog-does-matter">Why a blog does matter</h3>
<p>Blogs are the fundamental building block of today's internet. Think about it: we have giant corporations that create walled gardens that people are constantly drawn to, like Facebook, YouTube, Instagram, and Twitter.</p>
<p>Those are places that are engineered get you addicted so that you consume, debate, consume, debate, consume. And if you are in the lucky 1‰, you will be one of the stars of those walled gardens, and you will profit from other people consuming, debating, consuming, debating, consuming.</p>
<p>What are those people consuming? Well, aside from the content, they're consuming their time.</p>
<p>With notable exceptions, much of the content created and shared on the internet isn't useful. </p>
<p>But a blog is one of those rare places on the Internet where you can create something of value without a giant corporation benefitting from your work. You are the owner of your little corner of the world.</p>
<p>I'll soon tell you more about the "own your platform" concept, but here's the gist: a blog matters because it's 100% yours.</p>
<h3 id="heading-is-blogging-still-relevant">Is blogging still relevant?</h3>
<p>This is a legit question. Is blogging still relevant in today's world, dominated by social media and big platforms?</p>
<p>Can the underdog still find some space on the Internet? Don't we have too many sites already?</p>
<p>First, we'll never have too many sites. There's less than 1 person creating content for every 100 people. This is a rule for all media consumption: there are ~100 TV or radio stations in a country with 100 million people. Same for newspapers.</p>
<p>The same goes for the Internet. If you start creating, and you do your job well, eventually your content will surface and you will have success. Whatever success means to you.</p>
<p>This is possible with videos on YouTube, podcasts, and blogs, of course.</p>
<p>So why should you start a blog instead of making videos? It's a choice. We can't all be good at making videos, and we can't all have a voice made for podcasts. Similarly, we can't all be good at writing.</p>
<p>But if you happen to enjoy writing and you like the idea of having your own little corner on the Internet, then creating a blog might be what you are looking for.</p>
<h3 id="heading-should-i-have-a-personal-blog-or-a-companyproduct-blog">Should I have a personal blog or a company/product blog?</h3>
<p>If you are a freelancer, working solo, or you are a solopreneur, a company of one, you might have a tough decision ahead: should you create a personal blog or a company blog?</p>
<p>There's a big distinction in this. In my opinion, a personal blog is way better because companies and projects come and go. You might sell your company in 3 years.</p>
<p>Your personal brand, however, will stick with you wherever you go. The advantage I find in a personal brand is that you have the ability to experiment and pivot, if necessary. </p>
<p>If you create a brand that's too tied to a product or area, you can't switch without changing the name and domain, so it can be very confusing to people and it's very hard to make it work nicely.</p>
<p>And most likely you'll have a hard time finding a good idea, at first, so it’s always good to have more than one option. That way you don't have to start from zero.</p>
<h3 id="heading-why-a-blog-and-not-videos-or-podcasts">Why a blog and not videos or podcasts?</h3>
<p>In the last few years, videos and podcasts have grown tremendously.</p>
<p>Videos, in particular, are very popular. Kids want to become YouTubers when they grow up.</p>
<p>Both videos and podcasts are awesome. I watch YouTube every day and I am subscribed to several podcasts, too.</p>
<p>There's one thing I noticed about those platforms, however. Podcasts are very hard to discover. I only discover new podcasts if I see one mentioned somewhere. Or by word of mouth.</p>
<p>And in addition to having great content, as a podcast owner, a person's voice tone is a differentiating factor that makes me like a podcast or not. You are either born with a great podcasting voice, or you're not.</p>
<p>With videos, it's the same thing. A creator's personality is a unique differentiating factor in a great YouTube channel.</p>
<p>Plus, videos, if made well, require a huge effort in time, energy and equipment you need.</p>
<p>A blog, on the other hand, is something you can make no matter who you are, as long as you can write. You don't need to have that uniqueness that makes you liked on YouTube, and you are not trapped on one platform (YouTube). You also have a great channel for organic discovery of your blog posts (Google). More on this later.</p>
<p>But you don't have to choose one over the other, of course.</p>
<p>Many times creators use a blog alongside a YouTube channel, or a podcast. This is helpful if you use it more than just to cross-post content from one channel to another, but as a complementary tool.</p>
<p>When done well, it's a powerful combination.</p>
<h3 id="heading-define-who-your-blogs-readers-are">Define who your blog's readers are</h3>
<p>A blog that's successful and useful to other people is a blog that has a readership in mind.</p>
<p>I'm sure you've stumbled upon those blogs or sites that talk about a little bit of everything.</p>
<p>It all seems very random, and you don't find a reason for coming back to that site, so you forget about it.</p>
<p>If you happen to visit a website that talks just about your favorite topic, you might save it for later in your bookmarks, or leave the tab open to go back to it later.</p>
<p>This happens because the owner of the site is not just writing about everything that comes to mind: they have a specific kind of person they are talking to.</p>
<p>And this is what you should do, as well. Carefully define the kind of person you want to talk to with your blog.</p>
<p>You have to pick a niche.</p>
<h3 id="heading-how-to-pick-a-blogging-niche">How to Pick a Blogging Niche</h3>
<p>Picking a niche is how you can define what your little corner of the internet will be all about.</p>
<p>It can be all about you, your hobbies, passions, and stories. But then not many people will find it very useful. Maybe one post can solve their problems, but will they ever come back? Probably not.</p>
<p>Because your blog's not about you, it's about your readers.</p>
<p>Pick a niche. A small subject.</p>
<p>Say you're a programmer. You can write a blog about the C++ programming language, write 2 posts a week, and never run out of topics for 20 years. It's probably even a niche that's too broad, not much of a niche.</p>
<p>A better niche would be C++ programming for embedded devices. Or using C++ to build games.</p>
<p>Then filter out this group of people a little bit more. Using C++ to build a 2D game engine. </p>
<p>You can write about that for years, too, but this time if a person is interested in this subject, they will bookmark the site and download everything they can because you are the expert (or at least more of an expert than they are) and they can get a lot of value from you.</p>
<h3 id="heading-blogging-is-a-a-marathon-not-a-sprint">Blogging is a a marathon, not a sprint</h3>
<p>I want to give you another concept that will frame your blog for success: it’s not a sprint, it's a marathon.</p>
<p>As with everything in life, creating a successful blog takes time.</p>
<p>Creating a blog, per se, is a matter of hours if not minutes. This is why everyone can start a blog.</p>
<p>But creating a successful blog, depending on what you consider "success", can take years, and not many people welcome this fact with joy.</p>
<p>People like immediate, fast success. It almost never happens, unless you are very lucky.</p>
<p>Consider blogging to be your marathon.</p>
<h2 id="heading-the-benefits-of-having-a-successful-blog">The benefits of having a successful blog</h2>
<p>We all have limited time to dedicate to our hobbies, families, and friends. So why should you set aside a number of hours every week to work on your blog? And it's not just a few hours here and there. If you want the blog to be successful, you have to dedicate a lot of time.</p>
<p>As with everything in life, you have to evaluate the pros and cons.</p>
<p>Let's analyze the benefits.</p>
<h3 id="heading-personal-satisfaction-and-gratification">Personal satisfaction and gratification</h3>
<p>First, blogging gives you personal satisfaction and gratification.</p>
<p>Many people like to build puzzles or to do crosswords during their free time. Or they like to tinker with woodworking or electronic gadgets.</p>
<p>Writing on your blog can give you the same feeling.</p>
<p>Plus, it gives you a sense of accomplishment.</p>
<p>Writing a blog post can be easy sometimes.</p>
<p>Other times it's really hard, maybe because of the subject, or maybe because you can't find your way through your writing.</p>
<p>But when you finally finish it, it's a really good feeling.</p>
<h3 id="heading-you-can-get-your-name-out">You can get your name out</h3>
<p>Another thing that a blog can give you is discoverability.</p>
<p>When people search your name on Google, if you write under your own name people will find your blog as the first result. This gives a great impression at a job interview, for example. </p>
<p>And even if you are currently employed, having a blog is like an extension of your personal brand that can help you be perceived as a more knowledgeable person.</p>
<p>It's like writing a book. A book that no one reads is one thing – but writing a successful book that is read by many people is a completely different thing.</p>
<p>Blogs follow the same rule. Some blogs are not very popular, maybe due to the subject or other factors (we'll see more about this later). But some blogs are successful, and others are wildly successful.</p>
<h3 id="heading-you-can-get-in-touch-with-more-people">You can get in touch with more people</h3>
<p>By writing on your blog, you can publish things that are read by people all around the world, and your work can possibly help them achieve what they want.</p>
<p>Not only this is great for personal satisfaction and fulfillment. It's also a great way to be perceived as an expert in your field.</p>
<p>And the more people you can reach and impact, the better it is for you (and them!).</p>
<h3 id="heading-a-blog-is-your-platform">A blog is your platform</h3>
<p>Your platform, your launchpad. In today's world, there's just too much noise.</p>
<p>We are bombarded with information from every medium, and it's really hard, as a creator, to stand out.</p>
<p>A blog can be a great starting point for your next project, your next adventure, your next (or first) company. It's yours.</p>
<p>The goal of any product is to be used by people.</p>
<p>Lots of people, possibly. Not everything can "change the world" Silicon Valley-style, but your little project, product, or service can have a big impact on a tiny slice of the people it targets.</p>
<p>But the first steps are always hard, including finding a good idea.</p>
<p>Your blog can be the way you find the initial users for something that you find worth creating.</p>
<p>Put the idea out there. See how people react to it.</p>
<h3 id="heading-you-can-become-an-active-part-of-the-community">You can become an active part of the community</h3>
<p>In every community, there is a small number of people that lead, a slightly bigger number of people that contribute, and a larger number of people following.</p>
<p>Think about the community you are in. The 1%, or 0.1%, work on the "core". They organize an event,  build a software project, host a podcast, work on a product. Another 9% will participate and be an active part of the community around it. 90% of the people will just consume.</p>
<p>As a blogger, even if you're not in the 1% that lead, because you don't want that responsibility or power or you can't make the commitment, you can still contribute. You can add your input, and be heard in a world that is eager for people that want to stand out.</p>
<p>Write about that thing you are so enthusiastic about. Create a helpful resource. People will be really grateful for that. Including the 1% or 0.1% that lead but that do not have time, resources, or will to create those resources.</p>
<p>A notable example is Jeffrey Way. He created Laracasts to build training screencasts for Laravel, a programming framework that was gaining traction.</p>
<p>He’s now an integral part of the framework ecosystem and a driving force for its popularity.</p>
<h2 id="heading-pick-a-goal-for-your-blog">Pick a goal for your blog</h2>
<p>You can start a blog for multiple reasons. No one has the same ambitions, the same drive, or the same starting point.</p>
<h3 id="heading-you-want-to-get-a-job">You want to get a job</h3>
<p>One goal for a blog might be to help to get your first job.</p>
<p>Suppose you are getting ready for your first rounds of interviews as a Junior Frontend Developer. As you are learning the technology and experimenting, maybe following an online course, you document everything you are learning.</p>
<p>I think this is great for 3 reasons:</p>
<ol>
<li>you reinforce your learning</li>
<li>you build up an online presence</li>
<li>you get better at documenting and explaining technology</li>
</ol>
<p>As you approach the interview and send out applications, companies will see that you have a website. They'll find that you are great at explaining technology, you know how to communicate, and you definitely know your stuff.</p>
<p>And this is a great advantage over any other job applicant that does not have a website.</p>
<h3 id="heading-you-want-to-get-a-raise-or-a-better-job">You want to get a raise or a better job</h3>
<p>If you already have a job, you might want to step up your career and get better pay, or switch to another niche in the field you are interested in.</p>
<p>Having a blog that positions you as an expert in the thing you want to work on is definitely a big plus.</p>
<p>You can leverage your blog at your current company to reposition yourself as a senior developer.</p>
<p>Your colleagues will look up to you and your writing and will perceive you as an authority in the field.</p>
<h3 id="heading-you-want-to-get-bettermore-clients">You want to get better/more clients</h3>
<p>If you are not interested in getting a job and prefer being the independent freelancer with a queue of prospect clients lined up, blogging can be a game-changer for you.</p>
<p>I experienced this first-hand when I started my career in tech.</p>
<p>I started my career by finding work on freelancing sites. It was hard to compete with freelancers all around the world who were much more experienced than I was in many cases. And sometimes they were located in a lower cost of living area, which allowed them to provide the same service at a fraction of my rates.</p>
<p>I started a blog about the subject I was specializing on, an open-source CMS, written in Italian, my own language, and people from Italy slowly started to perceive me as an expert. They were more than willing to pay a premium to work with a person that spoke their language, worked in their timezone, and was one of the few experts in the field in the country.</p>
<p>My blog was key to this because people searched for topics they knew they needed help with – and they found me.</p>
<p>At some point, I was even able to turn down client proposals because I had too many requests. I could choose the ones that I believed were better for my business and more interesting to me.</p>
<h3 id="heading-you-want-to-demonstrate-your-expertise">You want to demonstrate your expertise</h3>
<p>A common benefit to either getting a job or freelancing clients is this: a blog helps you demonstrate your expertise and expose what you know to other people.</p>
<p>Some people are more naturally inclined to show off their abilities.</p>
<p>Sometimes, depending on culture and perception, we can even think of them as more capable than what they really are.</p>
<p>Some other people are less inclined, perhaps because of introversion or shyness.</p>
<p>Blogging is a great way to demonstrate your expertise even if you're not naturally inclined to raise your hand in public. This is because it's a medium that has very low friction compared to, for example, creating videos that show your face on YouTube.</p>
<h3 id="heading-you-want-to-document-your-learning">You want to document your learning</h3>
<p>A great goal for your blog could be to document your learning. Perhaps being more competitive in the job market is not something you are interested in right now, or you just want to write about your hobby.</p>
<p>A blog is a great track record for everything you learn.</p>
<p>I have a terrible memory, for example, and sometimes I just create a blog post to remember how I made something work.</p>
<p>I used to write notes on an app on my computer, but now I default to blog posts because I realized I can help other people by publishing my notes on the Internet.</p>
<h3 id="heading-you-want-to-structure-your-thoughts-and-learn-more">You want to structure your thoughts and learn more</h3>
<p>The way I used to learn best back in school was by creating very detailed notes about a subject.</p>
<p>However I only made them for subjects I was interested in, which in retrospect explains why I was good in some classes and bad in others.</p>
<p>You can use your blog as a way to learn better.</p>
<p>I am a huge proponent of learning through blogging because it works.</p>
<p>I use blogging as a way to learn a subject and at the same time help other people.</p>
<p>When I write a new blog post I am forced to create a mind map of the subject and I try to frame it, before writing about it.</p>
<p>In this way I learn much more than I do when I read a book or watch a course without taking organized notes about it. And as a side benefit, I end up with a new blog post I can publish.</p>
<h3 id="heading-you-want-to-become-better-at-explaining-things">You want to become better at explaining things</h3>
<p>A great benefit of blogging is that over time you'll become better at explaining things.</p>
<p>You will take fewer things for granted, and you'll think more from the point of view of the person who is listening to you, rather than just writing a bunch of words to be perceived as an expert.</p>
<p>This will help you tremendously in your career and as a person.</p>
<h3 id="heading-you-want-to-grow-your-audience">You want to grow your audience</h3>
<p>A great benefit of having your own blog is that over time you'll start to build an audience.</p>
<p>As with many things, the more time you dedicate to it, the more your audience will likely grow.</p>
<p>People might get to know you. Recognize your name. You will not become famous (except in rare cases), but this might not be what you want, either.</p>
<p>That's not what an audience is for. And honestly, I see many developers raise an eyebrow when marketing subjects come into play.</p>
<p>An audience is a great place to test your ideas. An audience can help you figure out something. An audience is your group of people, the people that trust you and that can help you move to the next level.</p>
<h3 id="heading-you-want-to-express-yourself">You want to express yourself</h3>
<p>A blog does not need to be a means to an end.</p>
<p>My favorite blogs are the ones that are playgrounds for creativity and expression. Especially when applied to programming and computers in general.</p>
<p>A blog is a great way to have a track record of all your past creative projects.</p>
<p>Your own centralized creative hub.</p>
<h2 id="heading-own-your-platform">Own your platform</h2>
<p>Your blog is the operational center for your future. Your platform, your headquarters. Everything else should bring you to your site. </p>
<p>Your Facebook page, YouTube channel, Twitter, Medium, your guest posts on other blogs, and the content you stream – all are tools to connect people with the content of your site on the Internet.</p>
<p>The website can be made with any technology you want. It does not really matter, provided that it’s on a domain that you own, and that you can change as desired.</p>
<p>You should have complete freedom over it: you have to be able to contact your users whenever you want, you must be able to extract all the data from this platform. Also, whenever you want to, you should be able to move away to a different platform with all your content.</p>
<p>Lastly, you do not have to be at the mercy of other people decisions.</p>
<h3 id="heading-the-platforms-of-other-people">The platforms of other people</h3>
<p>There are many ways to sell online. For each type of product, there are a number of options, and while sometimes you can sell things in the way you want, sometimes you need to work with a platform managed by someone else.</p>
<p>For example, if you sell digital books, you will, of course, sell them on Amazon Kindle, which probably represents 80% of the market.</p>
<p>Do you sell physical products? In addition to your own e-commerce, you will want to sell them on Amazon too, as it’s a huge market opportunity.</p>
<p>Do you make mobile apps? You will need to sell them on the App Store if you target Apple users, or on Google Play for Android.</p>
<p>Here, in some of these examples, it is clear that sometimes you have choices and sometimes you have to take a mandatory road to actually sell something. </p>
<p>You can’t distribute iPhone apps through your site. If you build desktop applications for Mac, on the other hand, this is possible, but you might also want to join the Mac App Store, which in this case is optional. Apple sets the rules, but it gives you some visibility that you would not get without being in the App Store.</p>
<p>All these great distribution systems such as the App Store, Amazon, Kindle, and many others like Etsy or Alibaba, are platforms. Other people’s platforms.</p>
<h3 id="heading-benefits-of-using-a-platform-managed-by-other-people">Benefits of using a platform managed by other people</h3>
<p>There are, of course, benefits to using a platform managed by other people. It provides a virtually limitless user base, customers visiting the platform already have an account, and in many cases they just need to press the buy button.</p>
<p>In general, not managing your own platform saves you worry about having to manage a huge number of things.</p>
<h3 id="heading-disadvantages-of-using-a-platform-managed-by-other-people">Disadvantages of using a platform managed by other people</h3>
<p>But there are disadvantages of using a platform, as well. You have to comply with the rules the owners impose, and the customers are not yours – they are the platform's customers. So you might struggle to create a relationship with your customers.</p>
<p>You also have little room for your branding, as everything could change from one day to the next. In some cases you have little control over your prices, and you'll have to pay commission to the platform for each sale you make.</p>
<h3 id="heading-the-platform-sets-the-rules">The platform sets the rules</h3>
<p>A platform is not just the place where people turn into customers, but it is also the place where you can find and interact with them. Facebook, Instagram, and Twitter are platforms, phenomenal platforms, but still platforms built by others.</p>
<p>A few years ago, having a Facebook page with 10,000 fans and writing a message on the page was enough to show this message to a vast percentage of those people. Unfortunately, this has changed today because the Facebook platform has changed. Now we are lucky if we reach 10% of our audience.</p>
<p>The only way to reach more people who like your page is to sponsor posts. So you have to pay Facebook to communicate with your own fans: this is an example of what it means to build on another property.</p>
<p>This is not to say that you should not have a Facebook page, that you don’t have to invest in Facebook Ads, or that you do not have to interact with people on Twitter.</p>
<p>You should do these things if you're trying to build an audience. The market imposes them on you, because you find people in these places. But always try to use these large containers to find people and bring them to you. </p>
<p>And it's important to always have a way out of a platform, and not to be completely dependent on it.</p>
<p>To do this, you have to push users to come to you through all the channels you can take advantage of.</p>
<p>You have to create your own platform.</p>
<h3 id="heading-use-your-own-domain-name">Use your own domain name</h3>
<p>Your domain name is very important. I use my own name for the domain: <a target="_blank" href="http://flaviocopes.com">flaviocopes.com</a>.</p>
<p>Your domain is like your address.</p>
<p>Except that a real-world address changes when you move somewhere else.</p>
<p>Your domain, on the other hand, is yours forever, regardless of your physical location. Like your email address.</p>
<p>When you publish things on your own website, under your own domain name, you are building virtual real estate and generating value that can last for years if not decades.</p>
<p>The way things work for search engines, now and for the foreseeable future, is that quality links pointing to your website make the domain more valuable.</p>
<p>If you publish under a domain name that's not yours, you are effectively working for other people, in exchange for something that might appear convenient for you, like a nice way to create the first page. </p>
<p>But if you take the time and put in some work beforehand to set up your own site and get your own domain, it can pay you a lot more in the future.</p>
<h2 id="heading-make-the-first-step">Make the first step</h2>
<p>The first step is always the hardest. In anything you want to do. Running. Coding. Cooking. Blogging.</p>
<p>In this chapter, I want to make sure you are well equipped for your first step when you’re ready to dive into blogging.</p>
<h3 id="heading-pick-a-domain-name">Pick a domain name</h3>
<p>There is one very important thing you need to do before you can create your blog. You need to choose a domain name.</p>
<p>You can notice 3 patterns for domain names. People have their own name/surname combination like I do with <a target="_blank" href="http://flaviocopes.com">flaviocopes.com</a>. Some people like to have a fantasy name or their nickname. Totally fine, too.</p>
<p>If it's cool and you can create a brand out of it, it's great. Like <a target="_blank" href="http://codinghorror.com">codinghorror.com</a>. Other people like to have a more general brand, like <a target="_blank" href="http://css-tricks.com">css-tricks.com</a>.</p>
<p>The good aspect of this is that people know the content of the blog just by looking at the domain. The downside is that you are kind of forced to stick to that topic forever. </p>
<p>For example, <a target="_blank" href="http://css-tricks.com">css-tricks.com</a> has long stopped talking exclusively about CSS, and other topics like JavaScript, SVG and others are very often covered.</p>
<p>This is kind of controversial, but I think your own name, or brand name with no direct attachment to anything specific, is the best and most future-proof way to create a domain name.</p>
<p>With one caveat: if you use your own name, you'll never be able to sell the site to someone else, or transform it into something "more" than just yourself with some guest posts here and there. But there are notable exceptions as well, like <a target="_blank" href="http://raywenderlich.com">raywenderlich.com</a>.</p>
<p>If you create a specific domain name that reminds people of a technology or hobby in particular, on the plus side you'll be able to sell it when it's successful to a bigger brand.</p>
<p>I started posting on my blog frequently by talking about the Go programming language. I later switched to a completely different programming language, JavaScript. </p>
<p>But because I used my own name and domain, I did not have to change anything. If I had a domain name like <em>startprogrammingingo.com</em> or something similar, there's no way I could've just switched to talking about JavaScript, or Arduino. </p>
<p>The whole project would have sunk because the day I stopped my work on Go, the site would just end its life with that very specific domain.</p>
<p>Since I use my own name for the domain, whatever I want to write about 10 or 20 years from now will be perfectly fine.</p>
<p>If you already have a domain, maybe used in old projects, and you think it can work fine, that's even better.</p>
<h3 id="heading-domain-authority">Domain Authority</h3>
<p>There's something called domain authority that is really important. Because that's how Google determines your site's value, and it's fundamental in how Google ranks web pages in its results.</p>
<p>One factor in domain authority is the domain's age. Older is more stable and more trustworthy to Google. </p>
<p>And when you're creating your domain, try to get the longest expiration date you can. That's a factor, too. Google basically detects your seriousness with the project. If your domain expires in 3 years and a competitor's in 3 days, maybe your domain is better in the long term. </p>
<p>I mean, we can't really know how the Google algorithms work, but we can try and make sure we tick all the checkboxes.</p>
<p>That can make the difference between a site that's never visited, and one that gets 10 visitors a day. Or one that gets a few hundred visits a day to one that gets tens of thousands.</p>
<h3 id="heading-choose-a-blogging-software-dont-build-it">Choose a blogging software (don't build it!)</h3>
<p>I personally use Hugo to power my blog. It is built using Go, it is very fast, and has some limitations on the things you can do. This means I can avoid distractions and I can focus on the writing.</p>
<p>I see many people, especially developers, who start creating their own blogging software from scratch.</p>
<p>I think that this a bad idea.</p>
<p>Why?</p>
<p>Because the moment you start is the time that you have the most energy. There will not be another moment in the lifetime of your blog where you'll have the same enthusiasm and drive to work on it.</p>
<p>And if you channel this energy towards something so futile, in this case, like building the software, then when it's time to actually write the content you'll have very little time, very little energy, and very little drive to craft great content.</p>
<p>Plus, you'll have to work on bugs, handle updates, and more, on your own. It's much easier to let others work on maintaining popular software used by thousands of people rather than reinventing the wheel.</p>
<h3 id="heading-choose-a-minimalistic-stock-design">Choose a minimalistic stock design</h3>
<p>The second mistake, I think, is to work on the design before working on the content.</p>
<p>Just as developers like to build their own software, design-oriented people want to create their own design. It's logical, I get it. But in this scenario, at the beginning, it's just a distraction.</p>
<p>My suggestion is: pick a stock theme, the simplest you can find.</p>
<p>The more minimal the theme is, the better.</p>
<p>Remember to keep the focus on the content. Not on the blog engine, not on the theme, not on plugins...on the content.</p>
<p>I have a couple blogs built using the default Ghost theme, which look very professional. In the niche they are built in no one knows what Ghost is – let alone do they know that it's the default theme.</p>
<p>Also, no one cares. Except you.</p>
<p>You can work on the design later on. There's always time for that.</p>
<h3 id="heading-create-the-first-3-posts">Create the first 3 posts</h3>
<p>Right after you have your blog idea, write the first 3 posts. You can write them in a normal plain text editor, and move them later to the blog platform you chose. </p>
<p>I like to use Bear (<a target="_blank" href="https://bear.app">https://bear.app</a>) to create my drafts. It autosaves, it is beautiful and intuitive to use. But it does not really matter. The thing I want you to focus on is those 3 posts.</p>
<p>Why exactly 3 posts?</p>
<p>Because now that you are so pumped to create your blog, it's the easiest time to write 3 great posts. If you create only one, maybe the second will be deferred until tomorrow, then to the day after.. and you'll be left with a blog with a single post.</p>
<p>Two posts... kinda the same. 3 posts seem like a good number to me.</p>
<p>Once you have those 3 posts, it's time to publish them. My advice, in this case, is not to publish them all together at the same time. Instead, you should schedule them.</p>
<h3 id="heading-schedule-the-first-3-posts-in-advance">Schedule the first 3 posts in advance</h3>
<p>I've had several people ask me how can I be so consistent with my blogging. The best suggestion I have is to create a habit. Habits are a very hip and popular topic nowadays, with many books published on the subject.</p>
<p>I'm not an expert, but I noticed that once a habit is established, it's really hard to break it.</p>
<p>Use this little hack to force yourself to write blog posts. Once you decide your perfect schedule (twice a week, once a week, twice a month, once a month...) then schedule the 3 posts you wrote so that you make a little buffer. </p>
<p>Suppose you write once a week. Publish the first post immediately, the second next week, the third the following week.</p>
<p>Now, you can relax and have a little party because you can launch the blog. Plus, you already have content written for the next 2 weeks and you can start planning other posts.</p>
<p>All the other posts you will write will be queued after the posts you wrote in the beginning.</p>
<p>Don't be tempted to add a new post in the middle of your planned schedule: there will be times when you can't write due to limited time, or you will have a low energy week. The buffer will be helpful to keep a consistent publishing schedule.</p>
<p>One thing that is key here is this: once the schedule gets full enough and you can look back at a long streak of posts, all very consistent in their publishing date, and you will start to feel a little pressure to not break the streak.</p>
<p>Transform this little pressure into a positive push towards staying consistent. If you have a weekly publishing routine, at the end of the year you will have 52 blog posts written, which would be a major accomplishment.</p>
<h3 id="heading-keep-going-this-way">Keep going this way</h3>
<p>If you do the things I mentioned in this chapter and you just keep going, you will have 99% more success with your blog than anyone else.</p>
<h2 id="heading-solutions-to-common-problems">Solutions to common problems</h2>
<h3 id="heading-who-am-i-to-write-about-this">Who am I to write about this?</h3>
<p>Let's say you are a student. By writing about the subject you are studying, you can reinforce your learning. Writing things down forces you to learn it really well.</p>
<p>Put your ego in chains: people don't care about you. People care about solving their problems. At least on their first point of contact with you, opinions, politics and controversial statements are detrimental to the user experience.</p>
<h3 id="heading-i-dont-know-enough-of-the-subject">I don't know enough of the subject</h3>
<p>You don't need to be an expert.</p>
<p>When I write a technical blog post, it does not really matter if I'm an expert at it. On the contrary, if I'm an expert in a field and I'm trying to write an introductory or beginner-friendly topic, I might not be able to explain that topic very well. This is because I cannot think like a beginner might when they don't know a topic, and so I might assume too many things and not explain that topic well.</p>
<p>If you're not an expert, do your best to absorb a topic and explain it to people that don't know about it. If you're an expert, on the other hand, writing content for other experts might be a good approach. Then you can mostly leave the entry-level content to people that might be in a position to explain it better.</p>
<h3 id="heading-analysis-paralysis">Analysis paralysis</h3>
<p>Just get it out there. Publishing a piece of content is much better than keeping it for months, revisiting it, thinking and thinking about it. Just do it.</p>
<p>It's ok to plan, but make sure you don't get trapped by analysis paralysis. The longer you think about it, the longer it will take you to start. Focus that energy on finding 3 good articles to write. That's the start.</p>
<h3 id="heading-dont-be-afraid-of-failing">Don't be afraid of failing</h3>
<p>Resistance is telling yourself that you are not good enough, that you will fail. This happens because you're about to do some creative work, you are about to be exposed to the public, to the opinions of others, and others will think you are not good enough.</p>
<p>Resistance is a powerful force. Especially if you write opinions or personal facts in your blog.</p>
<p>I have two suggestions.</p>
<p>The first is to talk about a topic that cannot be judged. Pick facts. No one can rant that a fact is not correct. Explain something you learned recently.</p>
<p>The second suggestion is to read <em>The War of Art</em> by Steven Pressfield, which is a mandatory read, in my opinion, for everyone trying to do creative work. It really goes into the root causes of resistance, and why you feel it.</p>
<h3 id="heading-i-am-not-good-at-writing">I am not good at writing</h3>
<p>Are you sure? Or is this just an excuse to bail out of the work that will open so many doors for you, and get back to your room?</p>
<p>This might be resistance kicking in, giving you a nice excuse for not writing.</p>
<p>Are you good at making videos, or feel great at talking to a microphone? Then make videos or start a podcast instead, but don't try to avoid any work that makes you vulnerable to the other people, just because you fear it.</p>
<p>But if you are <em>really</em> not good at writing, your writing will get better in just one way: by writing more.</p>
<h3 id="heading-i-dont-know-english-very-well">I don't know English very well</h3>
<p>If you want to write for an international audience in the western world, then you should blog in English. I highly recommend doing so, but in some cases, it might make more sense to write in your native language. </p>
<p>For example, if the niche you chose is too crowded and there's no one talking about it in your native language, then it might be an interesting opportunity to position yourself as an expert</p>
<p>If you'd like to blog in English, don't take a lack of English knowledge as an excuse. First, if you are reading this book, then it means you can read English and if you can read it, you can write it.</p>
<p>Also, unless you explicitly write for native English speakers, most of your readers might not be native English speakers, either.</p>
<p>If you are afraid your English is not good enough, and never start practicing, it will never become good.</p>
<p>My suggestion is to just start, and have someone who's really good at English review your posts, so you can learn over time.</p>
<h3 id="heading-dont-give-up-too-early">Don't give up too early</h3>
<p>Most blogs fail. This is a harsh truth. Blogs are abandoned, people forget to renew the domain name, people move on.</p>
<p>Blogs get into this situation because people gave up. They gave up on writing content consistently and frequently, because they did not see immediate gratification from doing so.</p>
<p>If you want your blog to have the chance to become successful, you have to give it the time it needs, and you need to be prepared right from the start.</p>
<h3 id="heading-dont-be-overwhelmed">Don’t be overwhelmed</h3>
<p>It's too easy to be overwhelmed by all the things you need to do. In addition to your day job and your family and friends, you need to find the time to blog. </p>
<p>But blogging is not just writing. You need to find the perfect image for that post, you need to share the post on social media, you need to reply to comments, think about future post topics, craft that perfect and eye-catching post title...the list never ends.</p>
<p>It's easy to feel overwhelmed.</p>
<p>That's why I take a minimalistic approach to blogging, which involves no pictures at all unless they're needed in the content of the post, and no comments. </p>
<p>Not having to manage comments frees up a lot of time and mental energy. It also has a nice side effect: I don't have to worry about what people think of my articles. I just focus on the writing.</p>
<h3 id="heading-one-step-at-a-time">One step at a time</h3>
<p>Don't think you have to rush and do everything at once: open the blog, write 100 posts, create a Twitter account, a Facebook page, an Instagram account, make YouTube videos… it's just too much. </p>
<p>Start with the simplest thing you can do, create a habit, make that habit stick in your day to day and expand from there.</p>
<h2 id="heading-content-is-king">Content is king</h2>
<p>Content matters. Content is the thing that matters the most, in the context of your blog.</p>
<p>Let's discuss some of the things I believe are fundamental pillars in a content-centered blog strategy.</p>
<h3 id="heading-write-to-your-people">Write to your people</h3>
<p>Let's say you are great at cooking. Write about your craft: cooking tips, ingredients, recipes, talking to other people that love cooking. Do you love knitting? Write for other knitters about techniques and everything new that you learn about knitting. Love dogs? Create a blog for dog owners.</p>
<p>Anything you do, the important thing is that you identify a group of people that are as passionate as you about a subject, and you write for them.</p>
<p>There's no value in what you write if it's not valuable to other people. Sure, you can write about something that's only appreciated by you, perhaps. But then there's no purpose in spending so much time and effort creating blog posts, is there?</p>
<h3 id="heading-focus-on-creating-value">Focus on creating value</h3>
<p>Once you identify the people you want to write to, it's key for you to focus on creating value. </p>
<p>Value can take many different forms, of course. It could be how-to posts. It could be sharing your specific experience with some technology. It could be teaching other people how your raw meat diet for your dog works. </p>
<p>It could be telling everyone about your recent trip and what you visited, in detail, so that when someone is searching for tips as they are organizing a vacation, they can find your useful information.</p>
<p>There is no fixed rule on what creates value.</p>
<p>Here's what I do to create value for the readers of my blog: I create easy-to-follow tutorials, written with a beginner's mindset. I do not take the perspective of an expert while writing them. </p>
<p>To do so, I learn a new thing, so I'm not an expert in it, and I create a practical guide. </p>
<p>I know it's very difficult as an expert to write for beginners about the thing we're expert in. This is because we've forgotten what it means to be a beginner and we make too many assumptions, forgetting to mention essential things that are key to understanding a topic.</p>
<p>Other times I describe how I fixed a problem that I just solved while working on some coding project. My memory is very fresh and I can remember what it's like to have the problem, so I can describe it very clearly.</p>
<p>Sometimes I create less practical posts where I talk about things that relate to my experience as a developer, or common struggles, or tips for productivity, for example. Those are the posts that are normally appreciated the most by my readers.</p>
<h3 id="heading-focus-on-being-useful-and-solving-problems">Focus on being useful and solving problems</h3>
<p>Your posts must solve a problem: each post on your blog must have a very specific goal.</p>
<p>This is key, and the post title must perfectly address the problem and solution.</p>
<p>When people search for how to solve that problem, you want your post to be in the top results.</p>
<p>And your content must be awesome.</p>
<p>Exactly what they are looking for.</p>
<p>Focus on creating the absolute best, most valuable content.</p>
<h3 id="heading-write-the-blog-post-that-would-have-helped-past-you">Write the blog post that would have helped past you</h3>
<p>One of my favorite things is writing a blog post that contains the solution to a problem I just solved.</p>
<p>Everyone else solves a problem and moves on.</p>
<p>If you dedicate 10 minutes to document how you solved the problem you had, you are helping other people that might have this problem next week, or next year, or 10 years from now.</p>
<p>They will be thankful that you wrote something that helped them.</p>
<p>And perhaps you can even help yourself in the future.</p>
<p>Sometimes I google for a problem and I find my own post, written many months ago. I completely forgot that I wrote it.</p>
<h3 id="heading-what-not-to-write-about">What not to write about</h3>
<p>One thing I recommend that you not write about is anything that doesn't help other people.</p>
<p>It's difficult to draw a line in the sand, but you should think about what you are writing.</p>
<p>Don't think of a blog like a diary. You <em>can</em> use it like a diary, posting your rants or opinions or what you ate for dinner.</p>
<p>But is it really helping or creating any value for other people?</p>
<p>I personally only try to write about things that I assume will help anyone. Even if a post helps just one person, then it's a good post for the blog.</p>
<h3 id="heading-teach-everything-you-know">Teach everything you know</h3>
<p>I learned this mantra, or motto, from Nathan Barry. He's famous for his book <em>Authority</em> and for being the founder of ConvertKit, a great tool for bloggers that have the need to build a newsletter.</p>
<p>He has this notion that building an audience is the secret hack to creating a successful business, and I really believe that. And to build an audience, the best way is to share, in one way or another, everything you know.</p>
<p>He did that in the form of books, first with two books about design, as that was his specialty. Then he wrote everything he learned about selling those books in another book.</p>
<p>I make my living teaching everything I know, and since there's a finite amount of things I do know at any finite point in time, I had to transition to teaching everything I learn. </p>
<p>I found that many people like the way I talk about stuff, with a very simple approach, and this beginner-oriented point of view has been beneficial to me.</p>
<p>Your approach can be different, but the “teach everything you know” mantra is one that you need to seriously think about.</p>
<h2 id="heading-practical-content-suggestions">Practical content suggestions</h2>
<p>After discussing why content matters, and why you should focus on creating great content, it's time to talk about some practical suggestions.</p>
<h3 id="heading-content-length-how-much-is-enough">Content length, how much is enough?</h3>
<p>It does not matter how long your articles are.</p>
<p>The Internet is full of marketing blogs that tell you the content must be 2000+ words long, 10000+ words long, and so on.</p>
<p>But it does not matter as long as your content solves a problem for a person.</p>
<p>I have posts that solve the problem in 50 words, and if this is the solution to someone's needs, that's great!</p>
<p>There is no need to dilute the content with useless words, and I'm sure you immediately notice when a blog post is trying to transform a 5-line answer into a 200-line one.</p>
<p>The same thing happens with videos, right? You are looking for something, but you need to watch 15 minutes of intro to get to the meat of the subject.</p>
<p>Do the opposite. Do what's great for your readers.</p>
<p>Plus, Google does not care.</p>
<h3 id="heading-keep-posts-focused">Keep posts focused</h3>
<p>When I say that Google does not care about content length, it's because I've observed this first-hand.</p>
<p>Some of my most visited blog posts are very short, yet they take a problem and they solve it, quickly.</p>
<p>And Google notices it. The key is solving a problem. Google notices that you are helping it help its users, and that's what happens when you keep your posts focused on a specific topic.</p>
<p>Some topics will perform better than others, some might be less popular, but as long as it's focused on a little problem and it solves it, it's great.</p>
<h3 id="heading-dont-try-to-appeal-to-everyone">Don't try to appeal to everyone</h3>
<p>You can't write great content for everyone. Pick your ideal person, or group of people, and write for them.</p>
<p>Pick your crowd.</p>
<p>Pick your tribe.</p>
<p>Ignore everything else.</p>
<h3 id="heading-dont-aim-for-perfection">Don't aim for perfection</h3>
<p>Done is better than perfect.</p>
<p>This is my mantra. As long as what I do is technically correct and does not have errors, it's all fine.</p>
<p>You don't need to overthink it. Focus on being good enough. That's when the point of diminishing returns starts.</p>
<p>Most of the time, good enough is already better than 95% of what's out there.</p>
<p>Don't let chasing perfection limit your production volume.</p>
<h3 id="heading-it-does-not-matter-if-someone-already-wrote-about-it">It does not matter if someone already wrote about it</h3>
<p>There are thousands of water brands. Thousands of brands of wine and beer. Thousands and thousands of hotels.</p>
<p>The more people write about a subject, the more interest there is around it, and the more people read about that subject.</p>
<p>If no one wrote about something, it might not mean you are a pioneer or “first", it might just mean no one cares about that topic.</p>
<h3 id="heading-you-dont-need-to-bring-something-new-to-the-table">You don't need to bring something new to the table</h3>
<p>If you worry that you don't have a unique spin on something, or a revolutionary idea, you might be limiting yourself.</p>
<p>Often, a 1% or 5% improvement over something that's already out there is enough.</p>
<p>Google was not the first search engine. It just worked better.</p>
<p>Basecamp was not the first project management tool. It just worked better.</p>
<p>Focus on being better, not new.</p>
<h3 id="heading-you-can-blog-about-anything">You can blog about anything</h3>
<p>I am a programmer and I blog about programming. If you’re a programmer too, you don’t just have to blog about programming.</p>
<p>There’s space for everything.</p>
<p>The topic does not matter, as long as you are passionate about it. There's no way you can force yourself to write about one subject for a long time without burning out.</p>
<p>Anywhere there’s a problem, you can bring solutions.</p>
<p>For me the niche was programming, for you it might be something else. Also, programming is even a bad niche as most developers consider even a little bit of marketing to be spam and mostly hate receiving emails (as they're using email every day and probably already receive a lot of it).</p>
<p>I think blogging, properly done, can be even more successful in less technical topics. It can work, as long as people search for those topics on the internet and have a place where you can find them like a big subreddit, a big Facebook group, or a popular online forum.</p>
<h2 id="heading-create-a-system">Create a system</h2>
<p>I am an engineer and I believe in systems. Humans are weak. Systems are strong. Humans with a system are on a good track.</p>
<p>If only we could have a system for our blog, everything would be simpler.</p>
<p>Turns out that we can definitely have a system, and in this section, I'm going to explain the system I use.</p>
<p>It might not be the perfect system for you, and I won’t pretend it’s a system that works in 100% of the cases. But it's a starting point, and by tweaking it you can create your own perfect system.</p>
<p>A system makes you consistent.</p>
<h3 id="heading-consistency-is-key">Consistency is key</h3>
<p>Why is consistency so important?</p>
<p>Because consistency is deeply entrenched in our lizard brains.</p>
<p>You are the one that makes one blog post every Tuesday. You're not the one that <em>tries</em> to write a blog post consistently each week. You're the one that just <em>does</em>.</p>
<p>It's not me, Flavio, saying so.</p>
<p>Let me share something from Robert Cialdini, author of the amazing book <em>Influence</em>: </p>
<blockquote>
<p><em>Once we have made a choice [...] we will encounter personal and interpersonal pressures to behave consistently with that behavior</em>.</p>
</blockquote>
<p>Once you have a series of 5, 10, 20 weeks of posting, you automatically become the one that posts every week. Or twice a week, or whatever schedule you chose.</p>
<p>That's the key to the system: consistency.</p>
<p>Have you ever heard of Rand Fishkin's Whiteboard Friday? That was a great video series about SEO that came out every Friday. Do you know about FunFunFunction? MPJ releases one video every Monday. Every one of his fans knows and waits for his "good Monday morning" every week. </p>
<p>I’m sure you know YouTubers that post on a pre-determined day. Like Saturday or Tuesday.</p>
<p>Everyone can find the time to create a blog post every week. But not everyone is willing to put in the work to create something every week. Will you?</p>
<h3 id="heading-schedule-posts-ahead-of-time">Schedule posts ahead of time</h3>
<p>As we just discussed, the single best thing you can do with your blog is stay consistent.</p>
<p>But staying consistent is hard.</p>
<p>One of the easiest ways to become inconsistent is to miss a day because you were sick, or because you went to a party, or because you took a vacation.</p>
<p>When you miss a day, and you forgive yourself for this, you will miss other days.</p>
<p>That's what I would do. If I let one day slip by without a post, well… nothing bad happens and I have a track record of missing one post.</p>
<p>Before I realize it, I am sure I will miss other posts, too. Just because I was too lazy one day, I ruined the consistency of my blog and now it's impossible to get that perfect streak of posts back.</p>
<p>How do you prevent that? By scheduling posts ahead of time.</p>
<p>Before you start publishing your blog, before unveiling it to the world and announcing it everyone, write 3 posts as we discussed before, and start building up your queue.</p>
<h3 id="heading-have-a-queue">Have a queue</h3>
<p>When I first started my blog, I discovered the notion of creating a queue by accident.</p>
<p>I happened to work on another project of mine, one that didn't take off as I expected it to.</p>
<p>This project involved creating some long guides, about topics I cared about.</p>
<p>So when I decided to shut down the project, I thought that it was a shame to abandon those long guides to their unknown fate.</p>
<p>So I cut each chapter into several blog posts, and I got about 15-20 of them.</p>
<p>I could have published them all on that day, but I randomly decided to queue them up. One today, one tomorrow, and so on.</p>
<p>A couple of days after that, I had an idea for a new post. I was a blogger now, right? And I queued it after all those posts.</p>
<p>The queue has never dried up since then, over 700 days ago. Some days I reached the end of the queue and I had to fill it up again from zero, but I never missed a day.</p>
<p>I just attribute this result to the system – the queue I've built up, and the shame that would result from having written all those posts in the past and then stopping.</p>
<p>I could not stop.</p>
<h3 id="heading-pick-a-schedule-frequency-you-can-sustain-over-the-long-run">Pick a schedule frequency you can sustain over the long run</h3>
<p>One key part of this scheduling and queuing system is frequency.</p>
<p>How frequently should you post?</p>
<p>My recommendation is to post with a frequency you can maintain over time, consistently.</p>
<p>Start with one day per week. Create a queue, as I suggested, and see if you can maintain this frequency over 2-3 months. Once you start having a queue that's too long, say a blog post you write today will be published 3 months from now, then you can start increasing the frequency.</p>
<p>It's always best to scale up the frequency than to post less frequently because you can't sustain the frequency you initially set.</p>
<p>The reason I focus a lot on consistency and frequency is that people get used to it and they learn to expect a post from you, and they look for it.</p>
<p>If you start missing posts and not sharing information as frequently, then people don't know when to expect a new post from you, and they feel lost and disconnected.</p>
<p>This happens with everything. I notice it with videos.</p>
<p>If you watch YouTube videos frequently, what do you think when a YouTuber that posts every day, and you watch every day, suddenly stops posting for a week?</p>
<p>I subscribe to a YouTuber that posts every Saturday, and every Saturday, unless I am out doing something, I'm there, at noon, watching his video.</p>
<h3 id="heading-ignore-quantity">Ignore quantity</h3>
<p>If you know beforehand that you have 3 hours a week to dedicate to blogging, don't force yourself to create too many posts.</p>
<p>Do one post a week, but do it well. Don't create many posts that are irrelevant or low quality, create one that you can be proud of.</p>
<p>Quality is perceived, and people will associate your worth with the quality of your work.</p>
<p>Be known for creating high quality and helpful content.</p>
<h3 id="heading-focus-on-the-process-not-the-outcome">Focus on the process, not the outcome</h3>
<p>One recommendation I have to be productive is to focus on the process.</p>
<p>I am focused on creating the blog post.</p>
<p>I am focused on creating ebooks.</p>
<p>I am not focused on how many people will read the blog post, or if they will like it.</p>
<p>That's not something I have control over. The best thing I can do is focus on my work and make sure it is the best work I can perform today.</p>
<h3 id="heading-keep-the-process-lean">Keep the process lean</h3>
<p>When thinking about my process, I like to simplify it.</p>
<p>I want to remove all the friction I can remove.</p>
<p>It all needs to be simple, fast, streamlined.</p>
<p>I don't want roadblocks.</p>
<p>I try my best to remove all the resistance and all that powers resistance.</p>
<p>There should be nothing between me and writing a great blog post.</p>
<h3 id="heading-keep-a-list-of-topics">Keep a list of topics</h3>
<p>I don't always have the time to write about something.</p>
<p>Sometimes I have an idea for a topic, and I write down the title of the article in my favorite writing application.</p>
<p>When it's time to write a new blog post, and I don't have an idea ready, I go through the list of titles and find one that is appealing to me in that specific moment.</p>
<h3 id="heading-capitalize-high-energy-days">Capitalize high energy days</h3>
<p>Some days I have really high energy for writing blog posts. Some days I have very little, if any, energy.</p>
<p>On those days when I have the fire, I can write a few blog posts in a row and code the rest of the day (as that's my job). I have fuel for everything.</p>
<p>I capitalize on those days. I sit there and I might write 5 posts, and put them in the queue.</p>
<p>The days I have low energy I am thankful for those high energy days and I am thankful that I did everything I could to get the most out of them.</p>
<h2 id="heading-how-to-get-people-to-your-blog">How to get people to your blog</h2>
<p>Having a blog is great and all, but having a blog that people actually read is deeply satisfying.</p>
<p>There’s a problem though: how do you get people to read your blog?</p>
<h3 id="heading-how-blog-traffic-works">How Blog Traffic Works</h3>
<p>In today's world, if you are not producing valuable and consistent content, you are invisible. There are just too many things pushed at us every day.</p>
<p>I dedicate from 30 minutes to 1 hour every day to the Internet. That means Social Media, mostly Twitter, RSS and content aggregators.</p>
<p>I always see the same people. Those people that are highly successful keep pushing out great content.</p>
<p>It might be an inspiring tweet, a great blog post that's shared a lot, or something that's just worth reading.</p>
<p>Unless you perform at the same level, you are not appearing in my feed because I only give it limited time. And I assume the vast majority of people do the same. People don't just spend the whole day on social media waiting for you.</p>
<h3 id="heading-share-your-work-where-the-people-are">Share your work where the people are</h3>
<p>Sharing your content is a great motivator especially when your blog is young and you don't have an audience yet (more on this later).</p>
<p>In this situation, you can't rely on organic traffic. To avoid losing motivation, it's key to get your first views by sharing your content. </p>
<p>You can do 2 things: share it with your own audience on social media, if you have an audience already, or use sites that provide an audience and try your luck.</p>
<p>Those sites include Reddit, Hacker News, Medium, and forums that specialize in the topic you are talking about. Word of caution: you might have your feelings hurt in some of those venues, so just be prepared for criticism.</p>
<h3 id="heading-organic-growth-is-the-only-reliable-solution-for-long-term-traffic">Organic growth is the only reliable solution for long term traffic</h3>
<p>You might have the proverbial lucky day, and your blog post goes viral in places like Reddit, Hacker News, or on Social Media.</p>
<p>But the key factor for your blog is organic traffic.</p>
<p>Organic traffic means Google will receive a search from a user and will decide that it should show them your blog post.</p>
<p>Organic traffic is a key factor to every website. Social media traffic is generally low quality, hard to get, and hard to maintain over time.</p>
<p>This is a long process. Google needs to trust you, and it can take some time.</p>
<p>My best suggestion to give Google a reason to show your blog posts is to consistently solve people’s problems.</p>
<h3 id="heading-solve-peoples-problems">Solve people’s problems</h3>
<p>How do you get Google (and other search engines) to send loads of traffic to your website?</p>
<p>What matters to Google is that it satisfies its own users.</p>
<p>And those are people searching.</p>
<p>People search in order to solve their own problems.</p>
<p>I see SEO people who suggest that you write 3000+ posts to rank on Google.</p>
<p>That’s a great tip if their goal is to discourage you from writing more. They call it long-form content.</p>
<p>Now, as a non-SEO person, but as a person that does things and observes what works and what doesn’t, I can say that if you solve a problem for a person with a 4-line blog post, Google will thank you by sending you more people who have that problem.</p>
<p>They know using their algorithms when a person found the answer they needed. This is Google’s job. Their job is to solve people’s problems by providing the perfect content they are looking for.</p>
<p>If you can provide that, Google will help you.</p>
<p>Not every post on your blog can solve a problem, of course. But if you have posts that solve problems, you’ll start to notice, as those are the posts that will get the most visitors.</p>
<p>Not every post must be small of course, and if long-form content is best for you, do that.</p>
<h3 id="heading-the-importance-of-links">The importance of links</h3>
<p>When we were talking before about choosing a good domain name, I mentioned domain authority. I said that an old domain name will likely have more domain authority.</p>
<p>What is domain authority?</p>
<p>Domain authority is a sort of score that search engines use to set the importance of a website, and it's determined by many different factors. The specific algorithms and metrics used are not publicly available, but there's one thing which is key to domain authority: links.</p>
<p>The more links to it that a website has, the more authority it has. But it's not that simple. The more authority the domain has that the link comes from, the more important the link is.</p>
<p>Links have very different weights. A link coming from Wikipedia has more power than a link from a random tweet. And search engines do take social metrics into consideration, too. We can't know for sure, but a link in a tweet from an influencer in your field has more weight than a tweet from a Twitter bot account.</p>
<p>Google (I say Google because it's the most important search engine, but others might do a similar thing) is also careful about the topic. If I link to a kitchen recipe site from my computer programming blog, that link is not going to have much value. </p>
<p>If a famous kitchen recipe blog links to a smaller kitchen recipe blog, then that's more relevant for Google, and it will give that link more meaning.</p>
<p>When it comes to links, it's important to get links from relevant and on-topic websites.</p>
<p>This is only something you can get by providing great and useful content.</p>
<h2 id="heading-expand-your-reach-with-an-email-list">Expand your reach with an email list</h2>
<p>Let’s discuss how to augment the experience you create for your blog visitors by using an email list.</p>
<h3 id="heading-what-is-an-email-list">What is an email list?</h3>
<p>Blogs are closely linked to newsletters and email lists. The two often go hand in hand.</p>
<p>A newsletter is the most effective way you can get in touch with your blog readers over time.</p>
<p>If you are not already familiar with the email list concept, you may be wondering whether in the era of Facebook, Instagram, Snapchat, and Twitter if you still need to use an old-style system like email.</p>
<p>In fact, email is a fundamental tool for any online business. Blogs are no exception.</p>
<p>Sending emails to a list of customers is not a system by which people commonly spam, as you might be thinking.</p>
<p>Especially if you're a developer, you need to realize that email is useful, you are not evil for sending emails, and that people want to hear from you.</p>
<p>Email is a cool system where people interested in your content or products can have more information about them, or about new products that you could provide in the future.</p>
<p>Email marketing is one of the most effective systems with which people are converted over time from visitors to customers: people in your list have explicitly asked to receive more information from you in the future.</p>
<p>This does not mean sending an email after 8 months asking them to buy your new product. Instead, the right way to approach this is to ask yourself how you can help your customers, in a consistent way.</p>
<p>Through the list, over time you can build customer loyalty, you create a relationship, and you keep in touch.</p>
<p>Your task with the list is to train people on the subject you are talking about, and to foster anticipation about upcoming news. But try to keep the focus on the customer rather than on you. Remember that a person will not be on your list forever – just as long as you can keep interest high.</p>
<p>So how do you get a person to join your email list in the first place?</p>
<p>With a small gift, called the lead magnet, which can be for example a report, a free ebook, exclusive access to premium content, an email course, or really any gift you can make.</p>
<p>The concept is simple, but it is crucial that the gears that make up this mechanism are calibrated to perfection and oiled regularly. Don't leave anything to chance if you do not want to be amateurish in the eyes of your customers.</p>
<p>It’s fundamental that you offer people what they want. The email list is considered by most online business owners as the most important asset of a business, and the only one that really is 100% yours.</p>
<h3 id="heading-a-typical-use-of-an-email-list">A typical use of an email list</h3>
<p>Let's look at an example. This is the system that most successful blogs use: </p>
<p>a person reaches your site coming from search engines or Facebook. Then a popup appears (more or less invasive) with the request that they enter their email to get a small gift. Then you’ll send them the gift via email, and the person enters this cycle where they will be offered a variety of products over time.</p>
<p>Usually, when a person gets on your list, they enter a phase called the <em>funnel</em>. The funnel is an automated process of warming, that is, the person get "heated" and is brought from being a random visitor to becoming a possible customer via a series of emails.</p>
<p>As soon as the person signs up, they will receive an email. The next day they will receive another, after 3 days another one, all according to predetermined logic. </p>
<p>Typically, these e-mail series terminate after 7-8 mails where a story is told and in the end, they will be offered a product to purchase. If the person doesn't buy right away, emails will continue to flow, and so on until the automated sequence ends.</p>
<p>There are many kinds of different sequence types and usually people do not invent anything new. Instead they use well-tested systems (which you can easily recognize once you are trained).</p>
<p>Once this sequence is over, the person's email is placed in another sequence, or in the general list, which is no longer automated. Then, the person managing the list (you, for example), will send an update email from time to time, say once a month, to make sure the person don’t forget about them.</p>
<p>In short, the author of the list tries to sell a product but if the recipient does not buy it, they will still remain in the list until they cancel the subscription. Because you never know – in the future they could decide to buy and become a customer.</p>
<h3 id="heading-how-to-use-an-email-list">How to use an email list</h3>
<p>Of course, there are several approaches to the list. Let's see some situations that you could apply in your case.</p>
<p>If you sell an ebook: your users are therefore your readers. You could add a link to your site, and use as a lead magnet a free ebook on a topic that you know the user is interested in. </p>
<p>On the list, you could initially present an upsell of other ebooks you've already written. Then you could insert the person into your monthly email, where you'll publish interesting links to the topic, and from time to time promote your new ebooks.</p>
<p>If you sell a product: for an info-product, it's the same process as a typical example used on blogs. Maybe as a lead magnet you use one or more chapters, or you have a video or something else related to the subject.</p>
<p>If you have a software product: the list could be made up of all the people who bought your software if you sell to them directly, which gets you their emails. Or you may want them to sign up for your application. </p>
<p>You could use the list to share updates, inform about the latest releases, cross-promote with other developers, and raise awareness on new products you’ll build over time.</p>
<h3 id="heading-where-to-host-your-email-list">Where to host your email list</h3>
<p>To manage an email list you need a specialized software.</p>
<p>There are many different kinds of email newsletter management tools.</p>
<p>Some are simple. Some are way too complex for your needs. You just need to find the perfect one for you.</p>
<p>The first separation I want to make is between self-hosted software and SAAS. Sendy (<a target="_blank" href="https://sendy.co">https://sendy.co</a>) is an example of a self-hosted software. You don't pay a monthly fee to use it, but you have to manage your own server, and you use a service, typically AWS, to actually send the emails.</p>
<p>ConvertKit is an example of a tool that is SAAS based. You pay them a monthly fee, and they take care of everything.</p>
<p>That tool, in particular, is targeted at bloggers, and it's really well made. I have used it for almost 2 years, and it's really great.</p>
<p>I have recently moved to Sendy, self-hosting my newsletter, because at some point the price, for me, was not sustainable.</p>
<p>To start with, I would definitely recommend ConvertKit. You don't need to overthink spending $29 or $49 a month if that's valuable to you.</p>
<p>But my list became big enough and I was not comfortable spending a lot of money on my email list hosting software.</p>
<p>And it's a business tool: I know several top-performing bloggers with great audiences that use it for lists in the hundreds of thousands, and I'm sure that the ROI (Return on Investment) is well worth not having to manage your own server.</p>
<p>Especially if you are new to managing servers.</p>
<p>ConvertKit is not the only service, of course. There's Mailchimp, Drip, and others.</p>
<p>Some tools let you start free. I think of TinyLetter (https://tinyletter.com/). That's what I started with, in the beginning, until I reached a few hundred subscribers. Then I moved to a tool called ButtonDown and I went from there.</p>
<h3 id="heading-keep-it-alive">Keep it alive</h3>
<p>As with blog posts frequency and consistency, the best advice I can give about mailing lists is to keep them consistent.</p>
<p>Do you write every 2 weeks? Fine. Keep doing it.</p>
<p>Do you write every month? Fine. Keep doing it.</p>
<p>The exact frequency is really personal. Some people write a new email for every post they make on their blog.</p>
<p>Some people email once a month with the list of posts. If you write once a month, however, people might forget who you are. This happened to me: I might subscribe to a newsletter and forget about it if you don't email me quite frequently.</p>
<p>Since I write one post per day, I found my sweet spot at one email every week, on Tuesday, and I always respect this frequency.</p>
<p>People know that Tuesday is "email from me" day.</p>
<h3 id="heading-the-lead-magnet">The lead magnet</h3>
<p>We discussed how having a newsletter is essential. It's also essential to have people signed up for it.</p>
<p>How can you have people decide to give a thing so precious as their email address to you?</p>
<p>The best way I know is to give them something in return. Don't expect people to sign up without a clear gain for them</p>
<p>You need to give an incentive to people to sign up for your newsletter.</p>
<p>This is the lead magnet that we discussed briefly earlier.</p>
<p>It can be a 1-page PDF cheat sheet. A report. A short mini-course of 10 videos. An email-based course.</p>
<p>The sky is the limit when it comes to what the lead magnet should be.</p>
<p>And there is no reason you should only have one lead magnet. You can have multiple.</p>
<p>In the ideal case, every blog post should have a specific lead magnet, but this is of course not always possible. I tend to create a lead magnet for each category of posts I'm writing.</p>
<h3 id="heading-how-i-discovered-lead-magnets">How I discovered lead magnets</h3>
<p>At the beginning of my blogging adventure (a little more than 2 years ago), my blog had zero traffic and Google ignored me.</p>
<p>I shared my posts on Reddit, and some people came and saw the site. I shared my posts on Twitter, where I had like 500 followers, many of them probably bots or inactive followers that I had collected in the 10 years I had been on Twitter.</p>
<p>If I had 10 or 15 visits a day, that was a good day.</p>
<p>After a while, I decided to create a newsletter, with a simple promise: every 2 weeks I'd send you links to all the posts I published. Like everyone does. That was good to get an email subscriber every other day, but that was it.</p>
<p>After some months, traffic grew a little and I was getting 4-5 subscribers every day. This was better.</p>
<p>Then I realized that people will not subscribe to a newsletter if they don't gain an advantage from that. This is a key point. I rarely subscribe to a newsletter just to “stay in touch”. Sometimes I do, and when this happens to you, that’s a quality email subscriber. Someone that really wants to hear from you.</p>
<p>But to get to this level requires people to have received a lot of value from you already, in one way or another. Maybe they already subscribe to your YouTube videos or your podcast. It’s hard for a complete stranger to subscribe.</p>
<p>So I changed my strategy from asking to "Join my newsletter" to offering people something of value. This lets the conversation begin with me offering a valuable asset.</p>
<p>In my case, the valuable asset, in the beginning, was a little PDF with a collection of the best blog posts I had written on a subject, nicely organized as an ebook. </p>
<p>Most people coming to my blog didn’t read more than one page, so I thought that creating a nice little ebook was a good way to show them what I had to offer. This increased signups a lot.</p>
<h2 id="heading-ups-and-downs">Ups and downs</h2>
<p>Having a blog is not all fun and games. There are many things that might give you a hard time on your journey.</p>
<p>Realizing this is a first step in the right direction.</p>
<h3 id="heading-writers-block">Writer's block</h3>
<p>Writer's block is a thing. I have no general advice on this, and whole books were written on the subject so there's a lot of better advice out there than what I could come up with.</p>
<p>My solution when it comes to not knowing what to write about is to pick a subject I want to learn, and start learning it in public. Right as I write this, I am learning a completely new thing (Arduino and electronics sensors) while re-discovering the old electronics topics I studied back in school.</p>
<p>I am shooting videos while I learn this thing, and writing blog posts on the topics that are better suited for blog posts (no, I'm not limiting myself to blogging, I also like doing videos).</p>
<p>If a topic is boring to me, I just don't write about it because if I'm bored writing, I will write a boring article. So I try to write about things I'm interested in and excited about.</p>
<h3 id="heading-imposter-syndrome">Imposter syndrome</h3>
<p>Every person, sooner or later, is going to suffer from this thing which we label <em>imposter syndrome.</em> You might have this feeling now, but you don’t know that it has a name attached.</p>
<p>Let me describe some situations where you might find imposter syndrome in the wild.</p>
<p>You are a developer, have no Computer Science degree, and you feel people that have the CS degree know a lot more than you, and you should get one too in order to be called a developer.</p>
<p>You work on a project and you call it a little side project, not a real project, because – you know – it’s just a simple app.</p>
<p>You constantly belittle yourself, and have low confidence in your abilities.</p>
<p>You think other developers know a lot more than you do.</p>
<p>You think someday someone will find out that you are not worth your job position, as you can’t solve the coding interview quiz #423 from a random book.</p>
<p>You think you don’t belong to the <em>coder’s club.</em></p>
<p>In the case of blogging, you’d like to start blogging but you fear other’s opinions and even think that you can’t add anything new to the table, so you don’t even start.</p>
<p>What's the solution to this problem?</p>
<p>I don't know if it applies to you, but when I start to feel this way, I try to put myself in the learner's shoes. I am not teaching. I am learning in public. Most of the time.</p>
<p>If this is not enough, realize how far you have come from where you started. Look back. There was a day when you could not even figure out how to start the computer. What code even was. You didn’t know you could actually create programs and make the computer do what you want.</p>
<p>Look at you now. You are the best version of yourself and yet you can be perfectly sure that tomorrow you’ll be an even better version. You are improving. Just like your blog posts.</p>
<h3 id="heading-working-in-public">Working in public</h3>
<p>Working in public can be nerve-wracking. Well, it <em>is</em> for me. You don't really know who is watching your work, and what they really think of it.</p>
<p>You're one step away from an intolerant expert judging your work as not relevant.</p>
<p>And maybe the creator of a library is looking at your tutorial on it, thinking you didn't get it right.</p>
<p>But looking at the opposite side of the spectrum, by learning in public you are forced to grow.</p>
<p>Step up the game.</p>
<p>You need to put a lot of effort to make your work great in the eyes of a lot of people.</p>
<h3 id="heading-burnout">Burnout</h3>
<p>It's easy to feel overwhelmed by all the things you are doing. Thinking about new content, writing the actual content, writing guest posts, trying to promote the content as much as you can – but without being spammy, and replying to comments and feedback.</p>
<p>This is not unique to blogging, of course. I have seen this even more in the YouTube space. There, it's even worse to me. You are putting your face and voice in front of a lot of people, instead of just your words and pictures like in a blog post.</p>
<p>My best advice to avoid burnout is to choose a minimalistic approach and to pick a topic that deeply interests you.</p>
<p>If you write about things you are passionate about, you will never have a shortage of things to write about. Your list will be 3 miles long.</p>
<p>Choosing a minimalistic approach means that you have to cut down anything that's not essential. I do not have comments on my blog. I need to care less about what people <em>think</em>, which has the drawback of creating less community, but also has the positive of causing me less stress.</p>
<p>Also, I do not generally promote the content I write on social media or other outlets, except if it is a special article that I really want to be seen, and I do not usually write guest posts on other blogs.</p>
<p>Such activities can be limited once the blog is up and running and you get a fair share of visits every day. So with the increase of views and stress given by many people looking at your work every day, you get the benefit of focus.</p>
<h3 id="heading-lower-your-expectations">Lower your expectations</h3>
<p>If you keep your expectations low, you will never be deluded. Don't expect your blog to be an overnight success. It will not happen. Just like it does not happen with a YouTube channel or a podcast. It's hard. Except for some lucky ones, maybe.</p>
<p>This is why blogging about things you are passionate about is beneficial. If you write about things you always wanted to write about, and will even write if no one is reading, then you are on the right track.</p>
<p>Then if success will ever come to you, you'll be more than ready for it.</p>
<h3 id="heading-trolls-and-negative-feedback">Trolls and negative feedback</h3>
<p>The Internet can be a wild place, and I am sure I don't have to explain this to you. When people write comments online, they can be mean. Sometimes. Most of the time they are not. </p>
<p>I do not have comments on my blog directly, but I do receive feedback via email and on Twitter. And on YouTube videos.</p>
<p>And the rare day I get one blog post featured on Reddit or Hacker News, two sites relevant for my content, I get very stressed about looking at comments.</p>
<p>It must be me, and most of the times comments are wildly positive, which is great. But I heard somewhere that our brain is much more receptive to negative feedback, and I can certainly confirm that I remember bad feedback more than positive feedback. It can take 10 positive comments to make up for a single negative one.</p>
<p>I might be over-sensitive but removing comments altogether from my blog removed the handbrake. I do not have to worry if some content does not resonate with people.</p>
<p>I wrote it, it's like that, I did my best to make sure it's "correct" to my best judgment, and I am off to the next one.</p>
<p>Your mileage might vary.</p>
<h3 id="heading-ignore-vanity-metrics">Ignore vanity metrics</h3>
<p>I have the incredible experience of having enough visitors to be surprised every day of the number of people visiting the blog. </p>
<p>I open Google Analytics at a random point in time during the day and I see the number of people. Then I get back to whatever I was doing. It's kind of addictive, but also useless.</p>
<p>Does that affect my day? No.</p>
<p>Does that change what I'm going to do next? No.</p>
<p>Same for email subscribers. Does it really matter how many people are on the list? No.</p>
<p>What matters the most is that people resonate with your ideas and learn from your work.</p>
<p>Even if it is just a handful of people.</p>
<p>The rest is useless.</p>
<h3 id="heading-blogging-is-lonely">Blogging is lonely</h3>
<p>Blogging is lonely, in the same way that writing a book is lonely. Also working in your secret laboratory is lonely. Being a YouTuber is lonely. Being a remote freelancer is lonely.</p>
<p>There's not much of a solution for this.</p>
<p>Other than accept it, and realize that some people might be more happy to be lonely. I am definitely an introvert and I thrive in a lonely environment, spending days in the silence.</p>
<p>That's probably why I like blogging.</p>
<h3 id="heading-it-takes-time">It takes time</h3>
<p>Let's say it out loud: it's going to take a lot of time. There is no way that your blog will be an overnight success. It will take many months and possibly years before you will see a rising tide of visitors to your blog. Maybe. If you did it alright.</p>
<p>This is a harsh truth, but it's one I think is necessary because I don't want you to believe that success is easy to achieve, and I want to set the right expectations. Time, persistence, showing up every day. This is what it takes, for any kind of success in any kind of activity.</p>
<p>It's a long game, but one that will eventually give you a lot of satisfaction.</p>
<h2 id="heading-making-money-with-a-blog">Making money with a blog</h2>
<p>A blog can be a great launch point for various projects that can make you money.</p>
<p>There’s absolutely no need for your blog to make money if you don’t want it to. But it’s good to know what your options are. In this last chapter, I’m going to explain at a high level the main options for “monetizing” a blog.</p>
<h3 id="heading-get-more-clients-for-your-business">Get more clients for your business</h3>
<p>The first and easiest way, I'd say, is to offer your services.</p>
<p>This especially works with non-location specific services. For example, if you are a plumber it might be harder. Not impossible, especially in large cities, but other channels could work better.</p>
<p>But if your business or activity is not linked to a specific location, a blog can be a great promotion for your business.</p>
<p>You can write articles that position you as an expert in the field you work in, and people will get in touch using the tools you give them: email, chat, or whatever you want.</p>
<p>I’ve seen this applied countless times, and it can lead to big opportunities in consulting. I have also experimented this first-hand when I started my career.</p>
<h3 id="heading-advertising-affiliates-and-sponsors">Advertising, affiliates, and sponsors</h3>
<p>When your blog starts to gain a consistent number of visitors, that’s when it can make you some money in what’s usually called “passive income”.</p>
<p>There are 3 ways, mostly: advertising, affiliates, and sponsors. This is the difference: advertising is banner or text ads that appear on your page. You typically add a code to the site and then forget about it.</p>
<p>There is a broker, like Google Ads or Mediavine or BuySellAds that will take care of everything for you. They'll find companies that want to show their ads, and their ads will be displayed on your blog. You get paid by ad views and/or ad clicks. It’s the simplest method you can find, although it usually requires a high number of page views to work.</p>
<p>Affiliates work in a different way: you get paid when people purchase a product coming from a link put on your website. </p>
<p>A common affiliate system is Amazon affiliate links. I’m sure you’ve seen them everywhere, under YouTube videos, under Instagram posts, and so on. </p>
<p>When Amazon makes a sale, you get a percentage of that. Same works for other smaller affiliate programs. </p>
<p>For affiliate links, you need to generate a specific link and depending on the frequency you have to do that, and the number of products or programs you promote, it will require more work on your part.</p>
<p>Sponsors work in a different way, and you typically need to get in touch with companies via email or phone. They will usually pay a flat fee for a month (or more) of sponsoring. You typically need to put a banner on the site or talk about their products in your posts.</p>
<h3 id="heading-selling-your-own-products">Selling your own products</h3>
<p>The system I like the most is using the blog to promote your own products.</p>
<p>It’s similar to promoting your services, but with products, it requires less work on your part and it can scale very easily.</p>
<p>Sure, you need a product beforehand. And you need a product that you know will be relevant and useful to people who read your posts.</p>
<p>But once you have it, you can promote it to your readers, without paying for ads, and without having to run an affiliate program yourself. Then, all your work – your blog and your products – are all aligned in the same direction: making useful things for the people you want to interact with.</p>
<h2 id="heading-final-words">Final words</h2>
<p>I hope this book was useful to you, and I really hope it might be the inspiration you need to create your own blog.</p>
<p><a target="_blank" href="https://flaviocopes.com/page/book-blog/">Download a PDF and ePub version of the <strong>How to Start a Blog</strong> book here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Technical Blogging Basics – How to Write Articles as a Developer ]]>
                </title>
                <description>
                    <![CDATA[ Software developers work on designing, coding, testing, and delivering the software we use every day. And whatever that developer's particular specialty, they know a lot about a lot of things – which means they should share that knowledge.  Publishin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/technical-blogging-basics/</link>
                <guid isPermaLink="false">66be0020eb957b90783c1c6d</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Blogging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technical writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing tips ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tapas Adhikary ]]>
                </dc:creator>
                <pubDate>Thu, 15 Apr 2021 16:35:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/04/freeCodeCamp-Cover-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Software developers work on designing, coding, testing, and delivering the software we use every day. And whatever that developer's particular specialty, they know a lot about a lot of things – which means they should share that knowledge. </p>
<p>Publishing articles and creating video content are great ways to share what we learn as developers. You may have your own blog or you may write for a publication. In either case, you should follow specific processes to write well and feel great about it.</p>
<p>This article will cover the fundamentals of blogging to help you write great articles while not dropping the ball as a developer.</p>
<h1 id="heading-tldr">TL;DR</h1>
<p>This tweet summarizes most of the points at a high level. However, we will talk about some real-life experiences and learning how to blog in more detail. Please keep reading and enjoy.</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/tapasadhikary/status/1378224989288062982"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<h1 id="heading-know-your-purpose">Know your purpose</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/intent.png" alt="Image" width="600" height="400" loading="lazy">
<em>Know your purpose</em></p>
<p>We all need a purpose when we start something and the motivation to continue it. The intent behind our actions and our motivation may be different for everyone. You need to find your intent or purpose that explains why you want to start a blog or share your content. </p>
<p>In most cases, the simple answer could be <code>Passion</code>. Fair enough! It could also be a business strategy, or perhaps you want to teach others. Maybe you want to keep learning – practically anything that gets you started will work.</p>
<p>As a developer, we learn something new all the time. It is close to impossible to memorize every piece of what we've learned. When we document those lessons and bits of information, we might as well make that knowledge reusable. </p>
<p>This is why writing an article about something you've learned recently is an excellent idea, and gives you very clear intent for your progressive documentation.</p>
<p>💡 <strong>Tips:</strong> Create a private GitHub repo with a markdown file. When you encounter something new, add a note about it (with code, if needed) in the file. </p>
<p>The contents of this file will then become an excellent source for your future articles. I maintain a file called TIL_2021.md (Things I Learned in the year 2021) for the same purpose.</p>
<p>When I decided on blogging, I intended to learn by sharing knowledge. If you want to learn something deeply, start teaching it. Blogging is a great way to do that.</p>
<h1 id="heading-find-your-motivation">Find Your Motivation</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/motivation.png" alt="Image" width="600" height="400" loading="lazy">
<em>Do not give up, stay motivated</em></p>
<p>Motivation can do wonders when you have it, but it can be hard to continue without it. As a content creator, your biggest motivation is likely to hear feedback from your readers. Positive feedback and constructive criticism always help you improve the content you create.</p>
<p>But there is a problem. Initially, you may not have very many readers to give you feedback. The chances of disappointment are higher if you are an individual blogger. So it helps to have a lot of self-motivation to sustain and continue your work.  </p>
<p>Remember – do not give up, stay motivated. As a developer, you have plenty to learn, share, and write about.</p>
<p>💡 <strong>Tips:</strong> If you want to start blogging as a developer, the developer community is helpful to stay connected and motivated. There are many incredible communities around like <a target="_blank" href="https://hashnode.com/@atapas/joinme">Hashnode</a>, <a target="_blank" href="https://dev.to/">Dev.to</a>, <a target="_blank" href="https://community.codenewbie.org/">Codenewbie</a>, <a target="_blank" href="https://hackernoon.com/">Hackernoon</a>, <a target="_blank" href="https://forum.freecodecamp.org/">freeCodeCamp</a>, <a target="_blank" href="https://girlswhocode.com/">GirlsWhoCode</a>, and many more.</p>
<h1 id="heading-do-your-research">Do Your Research</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/research.png" alt="Image" width="600" height="400" loading="lazy">
<em>Research is the key. Content rules all.</em></p>
<p>In blogging, content rules all. As developers, we may have multiple ideas, problem-solving steps, and new learnings we want to get down. But the most crucial part is being able to turn it into quality content. It's usually necessary to take the time required to research your topic thoroughly.</p>
<p>Let me take an example of content research here. Say you have solved a problem using <code>Linked List</code>, and it is the first time you have used one. You are so excited that want to share what you've learned. Here are a few points to consider:</p>
<ul>
<li>You need to understand <code>Linked List</code> generically and beyond the context of the problem you have solved.</li>
<li>You need to understand both the pros and cons of using <code>Linked List</code>.</li>
<li>You need to set up a few examples that demonstrate how best to use it.</li>
<li>You need to make sure you explain the way you've solved the problem clearly so people can use it in their own use cases.</li>
</ul>
<p>💡 <strong>Tips:</strong> Once you figure out what you need to know, you can learn about it from any well-established resource. Perform a search wionth <code>Google</code>, <code>Quora</code>, <code>Reddit</code>, and so on. <code>Stackoverflow</code> is another excellent platform to check for info on the topic as well. </p>
<p>Make sure you note down what you learn as you make progress. These notes will eventually turn into the article you write and publish.</p>
<h1 id="heading-plan-the-content-structure">Plan the Content Structure</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/plan.png" alt="Image" width="600" height="400" loading="lazy">
<em>Plan the content structure</em></p>
<p>Once you have your content research done, the next thing is to plan the structure of the article. Excellent article content can go ignored by readers if you have an inadequate and messy content structure. </p>
<p>Here are a few tips to help you structure your article content in a readable way:</p>
<ul>
<li><strong>Title:</strong> A compelling title can help influence many readers to visit your article. No matter what, people hardly miss reading the title of the article. Keeping it catchy will increase the traffic to your content.</li>
<li><strong>Cover Image:</strong> A relevant cover image makes your article very attractive. When you share your articles on social media like Twitter, Linkedin, or Reddit, a creative cover image may attract your readers.</li>
<li><strong>Introduction:</strong> this section describes the content at a high level. It can be an initial paragraph or a <a target="_blank" href="https://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn%27t_read">Tl;DR</a> section explaining what you plan to cover in the article.</li>
<li><strong>Headings and Sub Headings:</strong> You should break the content into logical sub-topics. To do that, create sections and provide relevant headings and sub-headings. For example, I have created multiple sections with headings like <code>Know Your Purpose</code>, <code>Find Your Motivation</code>, and so on in this article.</li>
<li><strong>Graphics: "</strong>A picture is worth a thousand words." This is often true, so think of supporting your content with some graphs, pictures, and so on.</li>
<li><strong>Summary:</strong> A summary section at the end helps your reader recap what they have learned from the article so far. It is also helpful for a returning reader to recollect the content by going through the summary.</li>
<li><strong>Important Links:</strong> You may want to end your article with a list of related links for further reading. You can use the same section to list the links to your previously published articles as well.</li>
</ul>
<p>💡 Tips: Try to use a consistent content structure for your articles. Your readers will get used to it and find it easy to follow.</p>
<h1 id="heading-writing-tools">Writing Tools</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/Tools.png" alt="Image" width="600" height="400" loading="lazy">
<em>Using tools to make you efficient and smarter</em></p>
<p>Creating quality content takes time. You can make yourself a productive and efficient content creator by using some of the tools available for free. Here are a few that you may find helpful,</p>
<p>⚒️ <a target="_blank" href="https://www.notion.so/">Notion</a>: this tool can help you manage your personal and professional work TODOs in an efficient manner. Anytime an article idea occurs to you or you solve an interesting problem, create a task in the tool. You can prioritize, schedule, and assign tasks with ease.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-29.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task planner with Notion</em></p>
<p>⚒️ <a target="_blank" href="http://grammarly.com">Grammarly</a>: If you are a non-native English speaker like me, there are times when you might not be familiar enough with the language's grammar rules. </p>
<p>In this case, a tool like <code>Grammarly</code> is a life-saver in many ways. It detects grammatical and spelling mistakes, suggests re-phrasing for complex sentences, corrects passive to active voice, and more. You can start with the free version and go for the premium based on your usage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-30.png" alt="Image" width="600" height="400" loading="lazy">
<em>Correction suggestion with Grammarly</em></p>
<p>⚒️ <a target="_blank" href="https://hemingwayapp.com/">Hemingway Editor</a>: This is another excellent tool to support you with your English writing. You can use this editor along with <code>Grammarly</code> if you want. I love the way it indicates inadequate use of adverbs, active/passive voice, and complicated words and phrases.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-31.png" alt="Image" width="600" height="400" loading="lazy">
<em>Categorized suggestions with the Hemmingway Editor</em></p>
<p>⚒️ <a target="_blank" href="https://www.canva.com/">Canva</a>: Canva is a tool where you make designs, art, and unleash your creativity. You can create cover images, article graphics, animated gifs, and more without any prior experience with <code>Canva</code>. The generous free plan is sufficient to get started.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-32.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create and organize your creativity using Canva</em></p>
<p>⚒️ <a target="_blank" href="https://pixteller.com/">Pixteller</a>: This is an alternate suggestion for creating cover images, graphics, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-33.png" alt="Image" width="600" height="400" loading="lazy">
<em>Pixteller to create images</em></p>
<p>⚒️ <a target="_blank" href="https://getsharex.com/downloads/">ShareX</a>: It is a super cool productivity tool for screen captures, making animated images, file sharing, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-34.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create, gifs, screenshots, videos</em></p>
<p>⚒️ <a target="_blank" href="https://obsproject.com/download">OBS Studio</a>: this is a free, open-source video recording and streaming tool. You may be wondering, why do I need this for blogging? </p>
<p>At times, you may want to create a video, upload it to YouTube or Vimeo, and link to it from your articles. You can use the OBS Studio tool to create quality videos with lots of customization options.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-35.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create high quality videos</em></p>
<p>⚒️ <a target="_blank" href="https://serpsim.com/">SERP Snippet Generator</a>: SERP (Search Engine Result Page) is the page we see after entering a query into search engines like Google or Bing. A SERP snippet generator helps you finalizing a suitable title and meta description of your article post before you publish it. </p>
<p>See the image below to figure out a title and description within a limit to show the search result correctly.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/serpsim_snapshot-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>SERP Generator</em></p>
<h1 id="heading-do-lots-of-proofreading">Do Lots of Proofreading</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/reading.png" alt="Image" width="600" height="400" loading="lazy">
<em>Review, re-review, re-re-review, and so on...</em></p>
<p>When you write something, you need to check for errors and get your article reviewed before publishing or sharing it. This process of checking and making sure your content is ready to publish is called proofreading. In general, you should check for:</p>
<ul>
<li>Spelling errors</li>
<li>Grammatical mistakes</li>
<li>Formatting issues</li>
<li>Punctuation</li>
<li>Accuracy</li>
<li>Language Consistency</li>
</ul>
<p>When it comes to proofreading and reviewing, here is a famous quote for some inspiration:</p>
<blockquote>
<p>“I've found the best way to revise your own work is to pretend that somebody else wrote it and then to rip the living sh*t out of it.” ― Don Roff</p>
</blockquote>
<h1 id="heading-publish-your-article">Publish Your Article</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/publish.png" alt="Image" width="600" height="400" loading="lazy">
<em>Don't keep it to yourself, just publish it</em></p>
<p>If you are happy with the article after your proofreading, the next logical step is to publish it. You may want to schedule it to publish on a particular day of the week, or you may want to publish right then – it is up to you. </p>
<p>As a general principle, it is better to publish when an article is ready from your side. Similarly, you should never rush to publish an article to meet a deadline.</p>
<p>💡 <strong>Tips:</strong> Publishing your article should be part of the entire plan. If you have to publish an article by a specific date, work backward to plan the content accordingly. Do not compromise the quality of the content in the rush of publishing it.</p>
<h1 id="heading-share-your-article-on-social-media">Share Your Article on Social Media</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/socialize.png" alt="Image" width="600" height="400" loading="lazy">
<em>Social media platforms are a big booster</em></p>
<p>Social media is an extremely powerful tool. And you should make positive use of it as a blogger. </p>
<p>Publishing your article may not be enough if you want it to reach as many potential readers as it can. It's therefore a good idea to share your article on various social media platforms. </p>
<p>Here a few platforms where you should share your article links:</p>
<ul>
<li><a target="_blank" href="https://twitter.com/">Twitter</a></li>
<li><a target="_blank" href="https://www.linkedin.com/feed/">LinkedIn</a></li>
<li><a target="_blank" href="https://www.reddit.com/">Reddit</a></li>
<li><a target="_blank" href="https://news.ycombinator.com/">HackerNews</a></li>
<li><a target="_blank" href="https://facebook.com/">FaceBook</a></li>
</ul>
<p>There are a few more platforms where link sharing alone may not work very well. You can create a cover image/graphic suitable to the topic and upload the image (and share the link) to places like <a target="_blank" href="https://www.instagram.com/">Instagram</a> and <a target="_blank" href="https://in.pinterest.com/">Pinterest</a> using the right hashtags.</p>
<p>💡 Tips: Make sure you follow the policies and guidelines specified by each social media platform when you share your blog link. If you don't, your account could be flagged or banned. </p>
<p>Another exciting way to share your content is by republishing it. You can republish your article on another blogging platform if you are allowed to do so. For example, an article written on the <code>Hashnode</code> platform can be republished on the <code>Dev.to</code> platform and vice-versa. </p>
<p>💡 <strong>Tips:</strong> You can set the <code>Canonical URL</code> to the link of the original article when you republish it. This is a way to tell the search engines like Google which is the original copy of the content and eliminate duplicate content.</p>
<h1 id="heading-good-blogging-platforms">Good Blogging Platforms</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/paltform.png" alt="Image" width="600" height="400" loading="lazy">
<em>A Blogging platform to get you started</em></p>
<p>Alright, now we know how to write an article and share it so others can read it. Now let's learn about a few blogging platforms where you can get started. </p>
<p>Here are a few platforms to start blogging and grow as part of the community.</p>
<ul>
<li><a target="_blank" href="https://hashnode.com/@atapas/joinme">Hashnode</a> </li>
<li><a target="_blank" href="https://dev.to/">DEV Community</a> </li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/">freeCodeCamp News</a>    </li>
<li><a target="_blank" href="https://hackernoon.com/">HACKERnoon</a> </li>
<li><a target="_blank" href="https://daily.dev/blog">daily.dev</a></li>
<li><a target="_blank" href="https://www.codenewbie.org/">Codenewbie Community</a></li>
<li><a target="_blank" href="https://www.educative.io/edpresso">Educative Edpresso Shorts</a> </li>
<li><a target="_blank" href="https://cofounderstown.com/">CoFoundersTown</a> </li>
</ul>
<p>There are many publications and organizations who hire and pay content creators. As a developer-blogger, this may open up many freelancing opportunities and let you get compensated for sharing your content. You can also contribute to open-source documentation and other projects.</p>
<h1 id="heading-in-summary">In Summary</h1>
<p>To summarize,</p>
<ul>
<li>Blogging as a developer is manageable as a side activity without compromising your work output.</li>
<li>Problems you've solved and your searches on Google, Quora, and Stackoverflow could be a helpful source of writing ideas.</li>
<li>Find your purpose before you start a blog and write articles. The intent behind your work can be trivial or more significant – either way is fine.</li>
<li>Stay motivated.</li>
<li>Use the right tools to make you a productive writer.</li>
<li>Plan your content structure, proofread your articles, and publish them.</li>
<li>Use social media as a tool to share your articles.</li>
<li>There are some fantastic blogging platforms out there. Give them a try and be part of the developer communities.</li>
<li>Keep learning, keep writing, and keep sharing.</li>
</ul>
<h1 id="heading-before-we-end">Before We End...</h1>
<p>I hope you've found this article insightful, and that it helps you start using these concepts more effectively in blogging. </p>
<p>Let's connect. You will find me active on <a target="_blank" href="https://twitter.com/tapasadhikary">Twitter (@tapasadhikary)</a>. Please feel free to give a follow.</p>
<p>You may also like these articles:</p>
<ul>
<li><a target="_blank" href="https://blog.greenroots.info/how-to-find-blog-content-ideas-effortlessly-ckghrjv5200o7rhs1ewn40102">How to find blog content ideas effortlessly?</a></li>
<li><a target="_blank" href="https://blog.greenroots.info/where-to-begin-some-practical-tips-from-a-beginner-ckcu5llil00ncw8s11dr1fh2w">Where to begin? Some practical tips from a beginner</a></li>
<li><a target="_blank" href="https://blog.greenroots.info/why-do-you-need-to-do-side-projects-as-a-developer-ckhn5m5km05teajs1fvjd7u5f">Why do you need to do Side Projects as A Developer?</a></li>
<li><a target="_blank" href="https://blog.greenroots.info/16-side-project-github-repositories-you-may-find-useful-ckk50hic406quhls1dui2d6sd">16 side project GitHub repositories you may find useful</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/learn-something-new-every-day-as-a-software-developer/">How to Learn Something New Every Day as a Software Developer</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Gatsby Starter Blog: How to Add Header Images to Posts with Support for Twitter Cards ]]>
                </title>
                <description>
                    <![CDATA[ By David Good If you're like me, you used Gatsby Starter Blog to kickstart your personal blog, made a few customizations, and then just rolled with it.  Adding new posts in the form of markdown is great. But it also means you rarely have a reason to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/gatsby-blog-header-image-twitter-card/</link>
                <guid isPermaLink="false">66d45e01680e33282da25e4d</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Gatsby ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GraphQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ open graph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 29 Dec 2020 00:33:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/12/freeCodeCamp-GatsbyBlogImageTwitterCard-5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By David Good</p>
<p>If you're like me, you used <a target="_blank" href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog">Gatsby Starter Blog</a> to kickstart your personal blog, made a few customizations, and then just rolled with it. </p>
<p>Adding new posts in the form of markdown is great. But it also means you rarely have a reason to look at the code. So when I decided to add header images to my posts with support for <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards">Twitter Cards</a>, I felt lost.</p>
<p>My requirements were to be able to add a large header image with a caption to a post as you can see here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://davidagood.com/dynamodb-enhanced-client-java-heterogeneous-item-collections/">https://davidagood.com/dynamodb-enhanced-client-java-heterogeneous-item-collections/</a></div>
<p>Furthermore, a tweet which contains a link to the post should "expand" into <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image">Twitter's Summary Card with Large Image</a>, like this:</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/helloworldless/status/1336323721254948864"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p>And finally, for posts which do not specify an image, a default image should be shown using <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary">Twitter's Summary Card</a>. Here's what that looks like where I've used my website logo as the default image:</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/helloworldless/status/1338482084445347844"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p><strong>Note:</strong> Twitter's docs state that a website logo should not be used for a card image (see <code>twitter:image</code> section <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary#reference">here</a>). I'll leave it to you to decide whether it makes sense to use a fixed image as a fallback like I have here.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Here are the five high-level steps which I will be guiding you through. I'll attempt to explain everything in depth and provide links to other resources along the way. That way you will build up your knowledge of Gatsby which you can draw from to tackle the later, more complicated steps.</p>
<ol>
<li>Add Document Metadata Tags</li>
<li>Source Default Image using GraphQL</li>
<li>Source Post-Specific Image Properties using GraphQL</li>
<li>Add Header Image to Blog Post Template</li>
<li>Add New Properties to Post's Frontmatter</li>
</ol>
<p>The tools which we'll be using to accomplish this all come out of the box with <a target="_blank" href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog">Gatsby Starter Blog</a>!</p>
<ul>
<li><a target="_blank" href="https://github.com/nfl/react-helmet">React Helmet</a> - Used in the <code>SEO</code> component to add meta tags to the document head to support Twitter Cards and other <a target="_blank" href="https://ogp.me/">Open Graph</a> tags</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-source-filesystem/">Gatsby Source Filesystem</a> - A "plugin for sourcing data into your Gatsby application from your local filesystem", images in our case</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-image/">Gatsby Image</a> - "a React component specially designed to work seamlessly with Gatsby’s GraphQL queries. It combines <a target="_blank" href="https://image-processing.gatsbyjs.org/">Gatsby’s native image processing</a> capabilities with advanced image loading techniques to easily and completely optimize image loading for your sites. <code>gatsby-image</code> uses <a target="_blank" href="https://www.gatsbyjs.com/packages/gatsby-plugin-sharp/">gatsby-plugin-sharp</a> to power its image transformations."</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-plugin-sharp/">Gatsby Plugin Sharp</a> - "Exposes several image processing functions built on the <a target="_blank" href="https://github.com/lovell/sharp">Sharp image processing library</a>". We use this for resizing images.</li>
</ul>
<h2 id="heading-how-to-add-document-metadata-tags">How to Add Document Metadata Tags</h2>
<p>First, we will wire up the HTML metadata tags which can be read by Twitter and any other platform or tool which understands <a target="_blank" href="https://ogp.me/">Open Graph</a> such as Google, Facebook, and WhatsApp. </p>
<p>Learn more about document metadata here: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">What’s in the head? Metadata in HTML</a>.</p>
<p>Open the <code>SEO</code> component in <code>src/components/seo.js</code>. The first thing to notice is that this is using <a target="_blank" href="https://github.com/nfl/react-helmet">React Helmet</a>, and it already has many Open Graph and Twitter meta tags like <code>og:title</code>, <code>twitter:description</code>. It even has a <code>twitter:card</code> tag with a value of "summary" which enables a basic Twitter Summary Card with no image:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-keyword">const</span> SEO = <span class="hljs-function">(<span class="hljs-params">{ description, lang, meta, title }</span>) =&gt;</span> { 
<span class="hljs-comment">// Details omitted for brevity </span>
<span class="hljs-keyword">return</span> ( 
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Helmet</span> 
        <span class="hljs-attr">htmlAttributes</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">lang</span> }} 
        <span class="hljs-attr">title</span>=<span class="hljs-string">{title}</span> 
        <span class="hljs-attr">titleTemplate</span>=<span class="hljs-string">{</span>`%<span class="hljs-attr">s</span> | ${<span class="hljs-attr">site.siteMetadata.title</span>}`} 
        <span class="hljs-attr">meta</span>=<span class="hljs-string">{[</span> 
            { <span class="hljs-attr">name:</span> `<span class="hljs-attr">description</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">metaDescription</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:title</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">title</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:description</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">metaDescription</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:type</span>`, <span class="hljs-attr">content:</span> `<span class="hljs-attr">website</span>`, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:card</span>`, <span class="hljs-attr">content:</span> `<span class="hljs-attr">summary</span>`, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:creator</span>`, 
              <span class="hljs-attr">content:</span> <span class="hljs-attr">site.siteMetadata.social.twitter</span>, }, 
            // <span class="hljs-attr">...</span></span></span>
</code></pre>
<p>Let's update this component:</p>
<ol>
<li>Add <code>imageUrl</code> and <code>imageAlt</code> parameters. These will be passed as props by the <code>BlogPostTemplate</code> component as we will see later. Note that that I've used "URL" in the prop name to convey the fact that this must be a fully-qualified URL. Relative paths are not supported for the OG image!</li>
<li>Construct the default image URL, <code>defaultImageUrl</code>. I've written a tiny utility function, <code>constructUrl</code>, to concatenate a base URL with a relative path. We will see where <code>data.ogImageDefault</code> comes from in the next section.</li>
<li>Add an <code>ogImageUrl</code> variable which takes the <code>imageSrcUrl</code> prop or, if that's not provided, defaults to <code>defaultImageUrl</code>.</li>
<li>Add objects to the <code>meta</code> array passed to the <code>Helmet</code> component: <code>og:image</code>, <code>twitter:card</code>, and <code>twitter:image:alt</code></li>
</ol>
<p>A few things to note here:</p>
<ol>
<li>Twitter does have its own <code>twitter:image</code> meta tag, but per the <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started#twitter-cards-and-open-graph">docs</a>, we don't need to add both the <code>og:image</code> and the <code>twitter:image</code> tag since Twitter's parser will fall back to the Open Graph tags.</li>
<li>Open Graph specifies the <code>meta</code> attributes <code>property</code> and <code>content</code> whereas Twitter specifies <code>name</code> and <code>content</code>, respectively. But again, the Twitter docs state that their parser will fall back to the Open Graph attributes. This is nice because we can maintain consistency and don't need a bunch of repetitive properties with the same values which we have to keep in sync.</li>
<li>Notable exceptions to using the <code>property</code> attribute on <code>meta</code> tags are any non-Open Graph tags like <code>description</code> which must use the <code>name</code> attribute. I encourage you to use <a target="_blank" href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a> which will identify basic issues with your SEO.</li>
</ol>
<pre><code class="lang-js"><span class="hljs-comment">// util.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> constructUrl = <span class="hljs-function">(<span class="hljs-params">baseUrl, path</span>) =&gt;</span>
  (!baseUrl || !path) ? <span class="hljs-literal">null</span> : <span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${path}</span>`</span>;

<span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-comment">// Step 1: Add props</span>
<span class="hljs-keyword">const</span> SEO = <span class="hljs-function">(<span class="hljs-params">{ description, lang, meta, title, imageUrl, imageAlt }</span>) =&gt;</span> { 

    <span class="hljs-keyword">const</span> data = useStaticQuery(
        <span class="hljs-comment">// This is explained next</span>
    );

    <span class="hljs-comment">// Step 2: Construct default image URL</span>
    <span class="hljs-comment">// ogImageDefault is explained next</span>
    <span class="hljs-keyword">const</span> defaultImageUrl = constructUrl(data.site.siteMetadata.siteUrl, data.ogImageDefault?.childImageSharp?.fixed?.src)

    <span class="hljs-comment">// Step 3: Add this</span>
    <span class="hljs-keyword">const</span> ogImageUrl = imageUrl || defaultImageUrl; 

    <span class="hljs-keyword">return</span> ( 
        <span class="hljs-comment">// Step 4: Add new meta objects</span>
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Helmet</span> 
            <span class="hljs-attr">htmlAttributes</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">lang</span> }} 
            <span class="hljs-attr">title</span>=<span class="hljs-string">{title}</span> 
            <span class="hljs-attr">titleTemplate</span>=<span class="hljs-string">{</span>`%<span class="hljs-attr">s</span> | ${<span class="hljs-attr">site.siteMetadata.title</span>}`} 
            <span class="hljs-attr">meta</span>=<span class="hljs-string">{[</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:image</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">ogImageUrl</span>, }, 

                // <span class="hljs-attr">If</span> <span class="hljs-attr">a</span> <span class="hljs-attr">post</span> <span class="hljs-attr">has</span> <span class="hljs-attr">an</span> <span class="hljs-attr">image</span>, <span class="hljs-attr">use</span> <span class="hljs-attr">the</span> <span class="hljs-attr">larger</span> <span class="hljs-attr">card.</span> 
                // <span class="hljs-attr">Otherwise</span> <span class="hljs-attr">the</span> <span class="hljs-attr">default</span> <span class="hljs-attr">image</span> <span class="hljs-attr">is</span> <span class="hljs-attr">just</span> 
                // <span class="hljs-attr">a</span> <span class="hljs-attr">small</span> <span class="hljs-attr">logo</span>, <span class="hljs-attr">so</span> <span class="hljs-attr">use</span> <span class="hljs-attr">the</span> <span class="hljs-attr">smaller</span> <span class="hljs-attr">card.</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:card</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">imageUrl</span> ? `<span class="hljs-attr">summary_large_image</span>` <span class="hljs-attr">:</span> `<span class="hljs-attr">summary</span>`, }, 

                // <span class="hljs-attr">Add</span> <span class="hljs-attr">image</span> <span class="hljs-attr">alt</span> <span class="hljs-attr">text</span>
                // <span class="hljs-attr">Falls</span> <span class="hljs-attr">back</span> <span class="hljs-attr">to</span> <span class="hljs-attr">default</span> <span class="hljs-attr">which</span> <span class="hljs-attr">describes</span> <span class="hljs-attr">the</span> <span class="hljs-attr">site</span> <span class="hljs-attr">logo</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:image:alt</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">imageAlt</span> || "<span class="hljs-attr">davidagood.com</span> <span class="hljs-attr">logo</span>", }, 
                // <span class="hljs-attr">...</span></span></span>
</code></pre>
<h2 id="heading-how-to-source-default-image-using-graphql">How to Source Default Image using GraphQL</h2>
<p>This is where Gatsby's filesystem and image processing capabilities come into play. Below is the <code>useStaticQuery</code> call GraphQL query from the <code>SEO</code> component. I've added the <code>ogImageDefault</code> portion and the <code>siteUrl</code> which is needed for the <code>constructUrl</code> call shown above.</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-keyword">const</span> data = useStaticQuery(
    graphql<span class="hljs-string">`
      query {
        site {
          siteMetadata {
            title
            description
            social {
              twitter
            }
            # Add this
            siteUrl
          }
        }
        # Add this
        ogImageDefault: file(relativePath: {eq: "icon.png"}) { 
          childImageSharp {
            fixed(height: 260, width: 260) {
              src
            }
          }
        }
      }
    `</span>,
);
</code></pre>
<h3 id="heading-graphql-file-and-image-processing-query-explained">GraphQL File and Image Processing Query Explained</h3>
<p>The top level node is <code>ogImageDefault</code>. This is a <a target="_blank" href="https://graphql.org/learn/queries/#aliases">GraphQL alias</a> for the <code>file</code> query which is applying a filter to find a file with relative path equal to <code>icon.png</code>. The name I've chosen, <code>ogImageDefault</code>, is completely arbitrary.</p>
<p>One key thing to understand here is what the <code>relativePath</code> is relative to. In other words, where is this file, <code>icon.png</code>? </p>
<p>Let me start by telling you the location of the file relative to the project root: <code>./content/assets/icon.png</code>. In the query, I haven't specified any relative path, just the filename. So how does Gatsby know where to find it? </p>
<p>Enter <code>[gatsby-source-filesystem](https://www.gatsbyjs.com/plugins/gatsby-source-filesystem/)</code>. If you look in <code>gatsby-config.js</code> you will see some config like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// gatsby-config.js </span>
<span class="hljs-built_in">module</span>.exports = { 
    <span class="hljs-comment">// siteMetadata: {...}, </span>
    <span class="hljs-attr">plugins</span>: [ 
        <span class="hljs-comment">// Other plugins omitted </span>
        { 
            <span class="hljs-attr">resolve</span>: <span class="hljs-string">`gatsby-source-filesystem`</span>, 
            <span class="hljs-attr">options</span>: { 
                <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/content/blog`</span>, 
                <span class="hljs-attr">name</span>: <span class="hljs-string">`blog`</span>, 
            }, 
        }, 
        { 
            <span class="hljs-attr">resolve</span>: <span class="hljs-string">`gatsby-source-filesystem`</span>, 
            <span class="hljs-attr">options</span>: { 
                <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/content/assets`</span>, 
                <span class="hljs-attr">name</span>: <span class="hljs-string">`assets`</span>, 
            }, 
        }, 
        <span class="hljs-comment">// ...</span>
</code></pre>
<p>What this is doing is registering these paths as "content roots" and giving them a name. So the name <code>blog</code> refers to <code>./content/blog</code> relative to the project root. And the name <code>assets</code> refers to <code>./content/assets</code> relative to the project root. You can use these names in queries by filtering on <code>sourceInstanceName</code>:</p>
<pre><code class="lang-graphql"><span class="hljs-comment"># http://localhost:8000/___graphql </span>
{ 
    allFile(<span class="hljs-symbol">filter:</span> {<span class="hljs-symbol">sourceInstanceName:</span> {<span class="hljs-symbol">eq:</span> <span class="hljs-string">"blog"</span>}}) { 
        edges { 
            node { 
                absolutePath 
                publicURL 
                sourceInstanceName 
            } 
        } 
    } 
}
</code></pre>
<p>The result of this query:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Result of allFiles query with sourceInstanceName filter </span>
{ 
    <span class="hljs-string">"data"</span>: { 
        <span class="hljs-string">"allFile"</span>: { 
            <span class="hljs-string">"edges"</span>: [{ 
                <span class="hljs-string">"node"</span>: { 
                    <span class="hljs-string">"absolutePath"</span>: <span class="hljs-string">"/home/dgood/IdeaProjects/davidagood.com/content/blog/clean-code-and-architecture/index.md"</span>, 
                    <span class="hljs-string">"publicURL"</span>: <span class="hljs-string">"/static/40bb02d938c4faf7f977dd66c1a399d2/index.md"</span>, 
                    <span class="hljs-string">"sourceInstanceName"</span>: <span class="hljs-string">"blog"</span> 
                } 
            }, 
            <span class="hljs-comment">// additional results...</span>
</code></pre>
<p>So back to <code>ogImageDefault</code>: the <code>relativePath</code> we provided was just <code>icon.png</code>, but the file is actually located at <code>./content/assets/icon.png</code>. </p>
<p>Gatsby was able to resolve to the file because we configured a "content root" at <code>./content/assets</code>. We could have specified the <code>sourceInstanceName</code> to remove any ambiguity as to which "content root" this file is located in. </p>
<p>In fact, I'm not sure how Gatsby would behave if the same relative path existed in multiple "content roots". </p>
<p>This would be a good opportunity to dig into the Gatsby's source code to understand how this all works, but I'll leave that to you!</p>
<p>Next up: what is <code>childImageSharp</code>? "Child" refers to this being a child node of a <code>File</code> node. "Image" is just like it sounds. "Sharp" is referring to the <a target="_blank" href="https://github.com/lovell/sharp">Sharp</a> image processing tool and corresponding Gatsby plugin, <a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-plugin-sharp/">gatsby-plugin-sharp</a>, which enables these image processing features.</p>
<p><code>fixed</code> means we want transform the image into an image of a fixed size. We specify the dimensions by passing parameters like this: <code>fixed(height: 260, width: 260)</code>. There are a few alternatives to <code>fixed</code> which we could use, one of which we will see below.</p>
<p>Finally, we only need the <code>src</code> property for the purposes of the Open Graph image meta tag.</p>
<h2 id="heading-how-to-source-post-specific-image-properties-using-graphql">How to Source Post-Specific Image Properties using GraphQL</h2>
<p>Following from above, we must update the <code>BlogPostTemplate</code> component to pass the <code>imageUrl</code> and <code>imageAlt</code> props to the <code>SEO</code> component. Again, we use the <code>constructUrl</code> utility to convert the relative path, <code>src</code>, into a URL. I explain the origin of these props' values below.</p>
<pre><code class="lang-js"><span class="hljs-comment">// util.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> constructUrl = <span class="hljs-function">(<span class="hljs-params">baseUrl, path</span>) =&gt;</span>
  (!baseUrl || !path) ? <span class="hljs-literal">null</span> : <span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${path}</span>`</span>;

<span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">const</span> BlogPostTemplate = <span class="hljs-function">(<span class="hljs-params">{ data, pageContext, location }</span>) =&gt;</span> { 
    <span class="hljs-comment">// Details omitted for brevity</span>
    <span class="hljs-keyword">return</span> ( 
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">location</span>=<span class="hljs-string">{location}</span> <span class="hljs-attr">title</span>=<span class="hljs-string">{data.site.siteMetadata.title}</span>&gt;</span> 
            <span class="hljs-tag">&lt;<span class="hljs-name">SEO</span> 
                <span class="hljs-attr">title</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.title}</span> 
                <span class="hljs-attr">description</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.description</span> || <span class="hljs-attr">data.markdownRemark.excerpt</span>} 
                <span class="hljs-attr">imageUrl</span>=<span class="hljs-string">{</span>
                    <span class="hljs-attr">constructUrl</span>(
                        <span class="hljs-attr">data.site.siteMetadata.siteUrl</span>, <span class="hljs-attr">data.markdownRemark.frontmatter.image</span>?<span class="hljs-attr">.childImageSharp</span>?<span class="hljs-attr">.fixed</span>?<span class="hljs-attr">.src</span>
                )} 
                <span class="hljs-attr">imageAlt</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.imageAlt}</span> /&gt;</span>
        // ...</span>
</code></pre>
<p>Sourcing the image alt text is straightforward: we add <code>imageAlt</code> as a property to the <code>frontmatter</code> portion of our <code>BlogPostTemplate</code> component's GraphQL query. This query is exported as a GraphQL tagged template. </p>
<p>The name of the exported constant is arbitrary. In my case it's <code>const pageQuery</code>. </p>
<p>This query gets executed for us by Gatsby, and the results are passed to the <code>BlogPostTemplate</code> component in the <code>data</code> prop. </p>
<p>This is explained in the Gatsby docs here: <a target="_blank" href="https://www.gatsbyjs.com/docs/how-to/querying-data/page-query/">Querying Data in Pages with GraphQL</a>.</p>
<p>In order to source the actual image, we use <code>childImageSharp</code> again but in a slightly different way than we saw above:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pageQuery = graphql<span class="hljs-string">`
    query BlogPostBySlug($slug: String!) {
      site {
        siteMetadata {
          title
          siteUrl
        }
      }
      markdownRemark(fields: {slug: {eq: $slug}}) {
        id
        excerpt(pruneLength: 160)
        html
        frontmatter {
          title
          date(formatString: "MMMM DD, YYYY")
          description
          # Add this
          image {
            childImageSharp {
              fixed(height: 600, width: 1200) {
                src
              }
              fluid(maxWidth: 700, maxHeight: 500) {
                ...GatsbyImageSharpFluid
              }
            }
          }
          # Add these
          imageAlt
          imageTitleHtml
        }
      }
    }
`</span>;
</code></pre>
<p>Here, <code>image</code> must match the name of the property we intend to set in the post's frontmatter. And the value of this property must be a path to a file <strong>relative to the post markdown file</strong>. </p>
<p>This is similar to what we did above using a GraphQL alias and the <code>file</code> query, but here it's implicit and being handled behind the scenes by Gatsby.</p>
<p>We specify the dimensions in the parameters to the <code>fixed</code> field. When choosing the dimensions, make sure any image you use is at least as big as the dimensions you specify here, and use these guideline from the <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image#reference">docs</a>:</p>
<blockquote>
<p>Images for this Card support an aspect ratio of 2:1 with minimum dimensions of 300x157 or maximum of 4096x4096 pixels</p>
</blockquote>
<p>We have also added the <code>fluid</code> property and a <a target="_blank" href="https://graphql.org/learn/queries/#fragments">GraphQL fragment</a>, <code>...GatsbyImageSharpFluid</code>, which retrieves all of the properties available on this node without having to enumerate them one by one. </p>
<p>The Gatsby Image component is <a target="_blank" href="https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-image/#images-that-stretch-across-a-fluid-container">designed to be used this way</a> in order to provide a responsive image experience using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">HTML's native responsive image capabilities</a>.</p>
<h2 id="heading-how-to-add-a-header-image-to-your-blog-post-template">How to Add a Header Image to your Blog Post Template</h2>
<p>With the GraphQL query updated and the results being passed to our component by Gatsby, we're ready to add the Gatsby Image import and the JSX for the header image and caption:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"gatsby-image"</span>;

<span class="hljs-comment">// Details omitted for brevity</span>

{data.markdownRemark.frontmatter.image?.childImageSharp?.fluid &amp;&amp;
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
            <span class="hljs-attr">fluid</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.image.childImageSharp.fluid}</span>
            <span class="hljs-attr">alt</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.imageAlt}</span> 
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
            <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                <span class="hljs-attr">textAlign:</span> "<span class="hljs-attr">center</span>",
                <span class="hljs-attr">fontSize:</span> "<span class="hljs-attr">14px</span>",
                <span class="hljs-attr">lineHeight:</span> "<span class="hljs-attr">28px</span>",
            }}
            <span class="hljs-attr">dangerouslySetInnerHTML</span>=<span class="hljs-string">{{</span> 
                <span class="hljs-attr">__html:</span> <span class="hljs-attr">data.markdownRemark.frontmatter.imageTitleHtml</span> 
            }} 
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">br</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">br</span>/&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
}
</code></pre>
<p>If the <code>image</code> or <code>imageAlt</code> properties are not set in a post's frontmatter, it won't cause any issues. Those properties will just be <code>null</code> in the post's <code>data</code> prop, for example <code>data.markdownRemark.frontmatter.image</code> and <code>data.markdownRemark.frontmatter.imageAlt</code>. </p>
<p>For that reason, I've used <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">optional chaining</a> when passing the <code>imageUrl</code> prop to the <code>SEO</code> component: <code>data.markdownRemark.frontmatter.image?.childImageSharp?.fixed?.src</code> and when optionally adding the header image component tree: <code>data.markdownRemark.frontmatter.image?.childImageSharp?.fluid</code>.</p>
<h2 id="heading-how-to-add-new-properties-to-a-posts-frontmatter">How to Add New Properties to a Post's Frontmatter</h2>
<p>Now all that's left is to add the actual image file, typically in the same directory as the markdown where we want to use it. Then we add the <code>image</code>, <code>imageAlt</code>, and <code>imageTitleHtml</code> properties to the post's frontmatter. </p>
<p>I've taken the suggested attribution HTML directly from <a target="_blank" href="https://unsplash.com/">Unsplash</a> and used it for the <code>imageTitleHtml</code>.</p>
<p>Remember: in this case, the image path is relative to the post markdown file.</p>
<pre><code class="lang-md">--- 
title: "Working with Heterogeneous Item Collections in the DynamoDB Enhanced Client for Java" 
date: "2020-12-07T01:51:34.815Z"
description: "Working with heterogeneous item collections with the Java SDKs can be tricky. Here we see how to handle 
them with the AWS SDK v2 for Java's Enhanced Client."
image: "./kevin-mueller-gGUiw8GNIFE-unsplash.jpg"
imageAlt: "Water droplets on black background"
imageTitleHtml: '<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span></span>Photo by <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unsplash.com/@kevinmueller?utm_source=unsplash<span class="hljs-symbol">&amp;amp;</span>utm_medium=referral<span class="hljs-symbol">&amp;amp;</span>utm_content=creditCopyText"</span>&gt;</span></span>Kevin Mueller<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span> on <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unsplash.com/?utm_source=unsplash<span class="hljs-symbol">&amp;amp;</span>utm_medium=referral<span class="hljs-symbol">&amp;amp;</span>utm_content=creditCopyText"</span>&gt;</span></span>Unsplash<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>'

--- 

// Markdown here...
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it – you did it! We covered quite a few concepts in this article. You should now be able to add header images to your blog posts and get nice Open Graph-based preview experiences on Twitter, Facebook, Google, WhatsApp, and more.</p>
<p>You can find the completed code on GitHub here:</p>
<ul>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/src/components/seo.js">SEO</a></li>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/src/templates/blog-post.js">BlogPostTemplate</a></li>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/content/blog/dynamodb-enhanced-client-java-heterogeneous-item-collections/index.md">Example post markdown</a></li>
</ul>
<p>Once you've implemented this and deployed it, you can use the <a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter Card Validator</a> to test the behavior before actually tweeting a link.</p>
<p>Coincidentally, I did experience some issues with cards not being displayed in tweets even though the Validator showed that they were working. </p>
<p>In one case, I tweeted a link in a reply, and there was no card at all—just the raw link. The next day, I tweeted the same link, and this time the card worked fine! </p>
<p>In another case, I was looking at my Twitter Profile page, and several of my tweets had the cards but the image was not being displayed. So I opened a Chrome Incognito window, and in that window the images were displayed as expected.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Start a Blog for Free with WordPress – Tutorial for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ This article will discuss how to start a blog with WordPress. WordPress is the world’s most popular blogging software and content management system in the world. If you need an introduction, you can learn all about the basics of WordPress in my previ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-start-a-blog-with-wordpress/</link>
                <guid isPermaLink="false">66bb5750f55324ca867c88db</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Blogging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technical writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WordPress ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jim Campbell ]]>
                </dc:creator>
                <pubDate>Mon, 30 Nov 2020 17:54:27 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/11/wordpress-589121_1920.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This article will discuss how to start a blog with WordPress. WordPress is the world’s most popular blogging software and content management system in the world.</p>
<p>If you need an introduction, you can learn all about the basics of WordPress in my previous article <a target="_blank" href="https://www.freecodecamp.org/news/what-is-wordpress/">here</a>.</p>
<p>In this post, we will discuss:</p>
<ul>
<li>Blogging basics</li>
<li>Why you should choose WordPress</li>
<li>Starting a blog for free with WordPress.com</li>
<li>Starting a self-hosted blog with WordPress.org</li>
</ul>
<p>Before discussing the steps to use WordPress to create a blog, I will start by taking time to explain the basics of blogging.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/laptop-593673_1280.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-a-blog-and-why-should-you-start-one">What is a blog and why should you start one?</h2>
<p>A blog is a discussion or informational website published on the internet. Blogs can be funny, informational, or inspiring and can be published for highly targeted and niche audiences or a broader array of readers (like freeCodeCamp.org/news).</p>
<blockquote>
<p>A blog (a truncation of "weblog") is a discussion or informational website published on the internet consisting of discrete, often informal diary-style text entries (posts). - <a target="_blank" href="https://en.wikipedia.org/wiki/Blog">Wikipedia</a></p>
</blockquote>
<p>If you are looking to start a blog (and I was once in your shoes) there are a few reasons that might be motivating you:</p>
<ol>
<li>It’s fun and educational.</li>
<li>You are super passionate about a topic and want to share your thoughts with readers about it.</li>
<li>You want to use your blog as a website to generate money.</li>
</ol>
<p>When I created my first blog it was a mix of wanting to share my perspective on a topic and learn more about publishing online (I was 13 and created a blog on <a target="_blank" href="https://en.wikipedia.org/wiki/Yahoo!_GeoCities">GeoCities</a> about video game cheat codes). </p>
<p>That experience has led me to create dozens of websites and now I create websites primarily with WordPress.</p>
<p>The current environment also serves as a catalyst to start a blog. People have more time at home, are reconsidering careers, thinking about pursing graduate school, and <a target="_blank" href="https://freecodecamp.org">learning new programming languages</a> in completely new and unexpected trends.</p>
<p>With the massive rise in working from home, people have had more time to devote to side projects and entrepreneurship. I expect that most people who are starting blogs today are doing so either because they have more time without a commute or they are taking the path of entrepreneurship as the world changes.</p>
<p>Getting started on a new venture has never been easier with a platform like WordPress. Companies like SuperHuman and EarlyBird are able to set-up simple websites that collect pre-registrations. A CMS like WordPress will allow you to start small and expand the capabilities of your website easily over time. </p>
<p>From small ideas to full digital transformation of your business, WordPress comes packed with power to achieve your dreams. No matter how small your idea, it’s easy to get started with WordPress.</p>
<h2 id="heading-why-should-you-choose-wordpress">Why should you choose WordPress?</h2>
<p>WordPress is the most popular blogging software and content management system in the world. It currently powers about <strong>half a billion</strong> websites.</p>
<blockquote>
<p>39% of the web is built on WordPress. More bloggers, small businesses, and Fortune 500 companies use WordPress than all other options combined. Join the millions of people that call WordPress.com home. -<a target="_blank" href="https://wordpress.com/">WordPress.com</a></p>
</blockquote>
<p>If you are looking for blogging software, WordPress is an obvious choice with 60% of the market.  </p>
<p>You have probably heard about or looked at other content managements like <a target="_blank" href="https://www.joomla.org/">Joomla</a>, <a target="_blank" href="http://drupal.org/">Drupal</a>, <a target="_blank" href="https://www.squarespace.com/">Squarespace</a>, <a target="_blank" href="https://www.shopify.com/">Shopify</a>, and <a target="_blank" href="https://www.wix.com/">Wix</a>. These content management systems offer great products, but here are 3 reasons to choose WordPress.</p>
<ol>
<li>500+ sites are built each day using WordPress while only 60-80 per day are built on platform like Shopify and Squarespace</li>
<li>You can get started for <a target="_blank" href="https://wordpress.com/start/"><strong>free</strong></a>.</li>
<li>WordPress has an <a target="_blank" href="https://developer.wordpress.org/">incredible developer ecosystem</a> and you can build your web development skills quickly with WordPress.</li>
</ol>
<p>So if I’ve convinced you to use WordPress, let’s discuss how to actually get started.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/wordpress-923188_1920-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Starting a blog with WordPress.org.</em></p>
<h2 id="heading-how-to-start-a-wordpress-blog-for-free-on-wordpresscom">How to start a WordPress blog for free on WordPress.com</h2>
<p>Most people choose to use WordPress.org to create a self-hosted website. More on that below. But you can get started for free by signing up at <a target="_blank" href="https://wordpress.com">WordPress.com</a>. You can learn more about the differences between WordPress.com and WordPress.org at <a target="_blank" href="https://www.wpbeginner.com/beginners-guide/self-hosted-wordpress-org-vs-free-wordpress-com-infograph/">WPBeginner</a>. </p>
<p>The free WordPress.com platform is a good choice to get started with WordPress. It is very user friendly and you will not have to worry about updates or backups. It’s free for up to 3GB of storage space. Beyond 3GB you will have to pay for more space. </p>
<p>WordPress.com’s free blogging platform will allow you to quickly get started.</p>
<p>Get started <a target="_blank" href="https://wordpress.com/start/">here</a> and enter your email, username, and password. </p>
<p>Once you are setup, you will see the WordPress admin backend. Here you can edit your site's contents, select a Theme, and review your site's stats. </p>
<p><img src="https://lh5.googleusercontent.com/-bjsAbPsMjGQG3hYLy2lCtWQc7abr8ZOugUCTIwvdVw1dRFl2jUrKKdgWyXO0mHaUty25IibFOoQ5T0gfcncNOcOR3OdX_-qbzPtzRILoBWXbBdNACwbaojvQOjeW4v5hYWdZcM-" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://wordpress.com">WordPress.com</a> dashboard for a free prototype of a website I built.</em></p>
<p>After customizing your site with easy-to-use visual editing tools, you are all set up to start publishing posts and sharing your content with the world!</p>
<p>The free platform available on WordPress.com is very much <a target="_blank" href="https://en.wikipedia.org/wiki/Freemium">freemium</a> and is designed to get you to upgrade to a paid plan. It is a great way to start playing around with the tool and get familiar with adding pages, posts, and editing content. </p>
<p>All free sites are given a domain name of <em>{something)</em>.wordpress.com. You can upgrade your plan to select your own domain name or transfer it from another service if you already own one. </p>
<p>The real power of WordPress is unlocked with a self-hosted version.</p>
<h2 id="heading-how-to-start-a-self-hosted-blog-with-wordpressorg">How to start a self-hosted blog with WordPress.org</h2>
<p>WordPress.org allows you to create a fully-functioning website with the same features as WordPress.com and no restrictions. WordPress.org is open-source and 100% free for anyone to use, but you will need to pay for two things before getting started.</p>
<p>To get started self-hosting your WordPress, websites you will need three things:</p>
<ol>
<li>A domain name</li>
<li>A hosting provider</li>
<li>A willingness to learn (that’s why you’re on freeCodeCamp!)</li>
</ol>
<p>You can purchase a domain name from a variety of registrars like NameCheap, Bluehost, and GoDaddy. Domains cost about $20 per year.</p>
<p>There are hundreds of hosting providers to choose from. It is easy to choose the same host as your registrar like Bluehost or GoDaddy. Recently, there has been a rise in WordPress-specific hosts like Kinsta that specialize in WordPress. </p>
<p>Once you have your shiny new domain name and hosting provider, you need to install WordPress. Your host will help you do this. </p>
<p>For the advanced developers, you can install WordPress on your own server to have total control over your WordPress installation for better speed and security control. </p>
<p>When installing WordPress, you will usually need to provide some basic site and user information:</p>
<ul>
<li>Site title (easily changed later)</li>
<li>Site tagline (easily changed later)</li>
<li>Admin username (can not be changed, pick something secure)</li>
<li>Admin password</li>
<li>Admin email</li>
</ul>
<p>When WordPress has been successfully installed on your website, you can access the backend at <em>yourdomain.com</em>/wp-admin. Enter in the login information you provided and you'll be taken to the new admin dashboard of your very own website!</p>
<p><img src="https://lh4.googleusercontent.com/PkFZXO16zArdrATwucLmoHZt2oE02vcMyrnj4tZZr5eBw00zHbHUzBFn3uNNRaEGAc5uKeT9T-Ze-hvMoq9vAekrgsmQdMMKt3oS2PKPlphl-sDSulXbkGrH-rpHmKWiPw45g6cP" alt="Image" width="600" height="400" loading="lazy">
<em>Via What Is the WordPress Admin Dashboard?</em></p>
<p>WordPress comes pre-installed with a theme (which is updated every year) and usually some demo content like a Sample Post and a Sample Page. This content is already available at your domain.</p>
<p>The next steps after successfully accessing the admin dashboard should be to add your customizations to the site. Start with a Theme and then edit your Pages and Posts.</p>
<h3 id="heading-how-to-choose-and-install-themes">How to choose and install themes</h3>
<p>Themes are a fundamental part of WordPress. A <a target="_blank" href="https://wordpress.org/support/article/using-themes/">WordPress Theme</a> is a collection of files that work together to produce a graphical interface with an underlying unifying design for a website. They are the look and feel of your website.</p>
<p>Themes can be endlessly customized, but starting with a great theme will make your life very easy. You can select from thousands of themes available on ThemeForest and <a target="_blank" href="https://wordpress.org/themes/">WordPress.org</a>.</p>
<p>Themes can be easily installed by uploading a zip file into the Wordpress dashboard. Changing the look of your website is as simple as clicking "activate" on the newly installed theme. </p>
<p>After adding some style to your WordPress website, it’s time to add your content.</p>
<h3 id="heading-how-to-add-pages-and-posts-to-your-wordpress-site">How to add Pages and Posts to your Wordpress site</h3>
<p>Pages and Posts make up the majority of your site’s content. Pages are your webpages like your homepage, about page, contact us, and so on. Posts are the more frequently updated “blogs” that can be constantly added and organized under WordPress <a target="_blank" href="https://www.wpbeginner.com/glossary/category/">Categories</a>.</p>
<p>Whatever you need your WordPress blog to be, it all starts with the content. </p>
<p>Whether it is a simple landing page to collect emails like Superhuman, a publishing superpower like <a target="_blank" href="https://techcrunch.com">TechCrunch</a>, the <a target="_blank" href="https://sweden.se/">official site of Sweden</a>, a financial comparison website, or The Official Star Wars Blog (all of these are WP websites), people want to see your content! So get started by adding pages and posts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/wordpress-552924_1920.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-wrapping-it-all-up">Wrapping it all up</h2>
<p>WordPress is one of the most popular software platforms in the world. </p>
<p>It is easy to set up and is endlessly customizable, making it a great choice for starting your own blog and website. Your web development skills will grow with your content and ideas as you get them out into the world. </p>
<p>Whether you use the free WordPress.com or a self-hosted version, WordPress is a great tool to use. So go get started!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Blog with Gatsby and Netlify CMS – A Complete Guide ]]>
                </title>
                <description>
                    <![CDATA[ By Mohammed Asker In this article, we are going to build a blog with Gatsby and Netlify CMS. You will learn how to install Gatsby on your computer and use it to quickly develop a super fast blog site. You are also going to learn how to add Netlify CM... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-blog-with-gatsby-and-netlify-cms/</link>
                <guid isPermaLink="false">66d46040d14641365a050921</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Gatsby ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Netlify ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 06 Oct 2020 15:16:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c984e740569d1a4ca1946.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Mohammed Asker</p>
<p>In this article, we are going to build a blog with Gatsby and Netlify CMS. You will learn how to install Gatsby on your computer and use it to quickly develop a super fast blog site.</p>
<p>You are also going to learn how to add Netlify CMS to your site by creating and configuring files, then connecting the CMS to your site through user authentication. </p>
<p>And finally, you'll learn how to access the CMS admin so that you can write your first blog post.</p>
<p>The complete code for this project can be found <a target="_blank" href="https://github.com/mohammedasker/foodblog">here</a>.</p>
<p>Here's a brief introduction to these tools.</p>
<h2 id="heading-what-is-gatsby">What is Gatsby?</h2>
<p><a target="_blank" href="https://www.gatsbyjs.com/">Gatsby</a> is a free and open-source framework based on React that helps you build fast websites and web apps. It is also a static site generator like Next.js, Hugo, and Jekyll.</p>
<p>It includes SEO (Search Engine Optimization), accessibility, and performance optimization from the get-go. This means that it will take you less time to build production-ready web apps than if you were building with React alone.</p>
<h2 id="heading-what-is-netlify-cms">What is Netlify CMS?</h2>
<p><a target="_blank" href="https://www.netlifycms.org/">Netlify CMS</a> is a CMS (Content Management System) for static site generators. It is built by the same people who made <a target="_blank" href="https://www.netlify.com/">Netlify</a>. It allows you to create and edit content as if it was WordPress, but it's a much simpler and user-friendly interface.</p>
<p>The main benefit of Netlify CMS is you don't have to create markdown files every time you want to write a post. This is useful for content writers who don't want to deal with code, text editors, repositories, and anything to do with tech - they can just focus on writing articles.</p>
<p>Alright, without any further ado, let's start building the blog!</p>
<p><strong>But before we get going, a quick heads up</strong>: This guide requires prior knowledge of JavaScript and React. If you are not comfortable with these tools yet, I've linked the resources at the end of the article to help you brush up on those skills. </p>
<p>Even if you're new to those technologies, I tried to make this guide as simple as I was able so you can follow along.</p>
<h2 id="heading-how-to-set-up-the-environment">How to set up the environment</h2>
<p>Before we can build Gatsby sites, we have to make sure that we have installed all the right software required for the blog.</p>
<h3 id="heading-install-nodejs">Install Node.js</h3>
<p>Node.js is an environment that can run JavaScript code outside of a web browser.</p>
<p>It is a tool that allows you to write backend server code instead of using other programming languages such as Python, Java, or PHP. Gatsby is built with Node.js and that's why we need to install it on our computer.</p>
<p>To install Node.js, go to the <a target="_blank" href="https://nodejs.org/en/download/">download page</a> and download it based on your operating system. </p>
<p>When you are done following the installation prompts, open the terminal and run <code>node -v</code> to check if it was installed correctly. Currently, the version should be 12.18.4 and above.</p>
<h3 id="heading-install-git">Install Git</h3>
<p>Git is a free and open-source distributed version control system that helps you manage your coding projects efficiently. </p>
<p>Gatsby starter uses Git to download and install its required files and that's why you need to have Git on your computer.</p>
<p>To install Git, follow the instructions based on your operating system:</p>
<ul>
<li><a target="_blank" href="https://www.atlassian.com/git/tutorials/install-git#mac-os-x">Install Git on Mac OS</a></li>
<li><a target="_blank" href="https://www.atlassian.com/git/tutorials/install-git#windows">Install Git on Windows</a></li>
<li><a target="_blank" href="https://www.atlassian.com/git/tutorials/install-git#linux">Install Git on Linux</a></li>
</ul>
<h3 id="heading-install-gatsby-cli">Install Gatsby CLI</h3>
<p>Gatsby CLI (Command Line Interface) is the tool that lets you build Gatsby-powered sites. By running this command, we can install any Gatsby sites and the plugins we want.</p>
<p>To install Gatsby CLI, open the terminal and run this command:</p>
<pre><code>npm install -g gatsby-cli
</code></pre><p>Once everything is set up successfully then we are ready to build our first Gatsby site.</p>
<h2 id="heading-how-to-build-a-gatsby-site">How to build a Gatsby site</h2>
<p>In this guide, we're going to use the default Gatsby starter theme, but you're free to choose any themes on the <a target="_blank" href="https://www.gatsbyjs.com/starters/?v=2">Gatsby starter library</a>. I personally use the <a target="_blank" href="https://github.com/LekoArts/gatsby-starter-minimal-blog">Lekoart theme</a> because the design is minimalist and beautiful, and it has a dark mode.</p>
<p>In the terminal, run this command to install the new Gatsby blog:</p>
<pre><code>gatsby <span class="hljs-keyword">new</span> foodblog https:<span class="hljs-comment">//github.com/gatsbyjs/gatsby-starter-blog</span>
</code></pre><p><strong>Note for Windows users</strong>: If you encounter "Error: Command failed with exit code 1: yarnpkg" while creating Gatsby site, see <a target="_blank" href="https://github.com/gatsbyjs/gatsby/issues/26804">this page</a> to troubleshoot it. You may have to clean up dependencies of old yarn installations or follow the Gatsby on Windows instructions.</p>
<p>What's does this command line mean exactly? Let me explain.</p>
<ul>
<li><strong>new</strong> - This is the command line that creates a new Gatsby project</li>
<li><strong>foodblog</strong> - This is the name of the project. You can name it whatever you want here. I named this project <em>foodblog</em> as an example only.</li>
<li><strong>The URL</strong> (<a target="_blank" href="https://github.com/gatsbyjs/gatsby-starter-blog">https://github.com/gatsbyjs/gatsby-starter-blog</a>) - This URL specified points to a code repository that holds the starter code you want to use. In other words, I picked the theme for the project.</li>
</ul>
<p>Once the installation is complete, we'll run the <code>cd foodblog</code> command which will take us to the location of our project file.</p>
<pre><code>cd foodblog
</code></pre><p>Then we'll run <code>gatsby develop</code> that will start running on the local machine. Depending on the specs of your computer, it will take a little while before it is fully started.</p>
<pre><code>gatsby develop
</code></pre><p>Open a new tab in your browser and go to <code>http://localhost:8000/</code>. You should now see your new Gatsby site!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--33-.png" alt="Image" width="600" height="400" loading="lazy">
<em>How a Gatsby starter blog homepage looks</em></p>
<p>Now that we've created the blog, the next step is to add Netlify CMS to make writing blog posts easier.</p>
<h2 id="heading-how-to-add-netlify-cms-to-your-site">How to add Netlify CMS to your site</h2>
<p>Adding Netlify CMS to your Gatsby site involves 4 major steps:</p>
<ul>
<li>app file structure, </li>
<li>configuration, </li>
<li>authentication, and </li>
<li>accessing the CMS.</li>
</ul>
<p>Let's tackle each of these stages one at a time.</p>
<h3 id="heading-how-to-set-up-the-apps-file-structure">How to set up the app's file structure</h3>
<p>This section deals with the file structure of your project. We are going to create files that will contain all Netlify CMS codes.</p>
<p>When you open your text editor, you will see a lot of files. You can read <a target="_blank" href="https://github.com/gatsbyjs/gatsby-starter-blog#-whats-inside">this article</a> if you are curious about what each of these files does.</p>
<pre><code>├── node_modules
├── src
├── <span class="hljs-keyword">static</span>
├── .gitignore
├── .prettierrc
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── LICENSE
├── package-lock.json
├── package.json
└── README.md
</code></pre><p>Do not worry about all these files — we are going to use very few of them here. </p>
<p>What we are looking for is the <code>static</code> folder. This is the folder where it will form the main structure of the Netlify CMS. </p>
<p>If your project does not have <code>Static</code> folder, then create the folder at the root directory of your project.</p>
<p>Inside the <code>static</code> folder, create an <code>admin</code> folder. Inside this folder, create two files <code>index.html</code> and <code>config.yml</code>:</p>
<pre><code>admin
 ├ index.html
 └ config.yml
</code></pre><p>The first file, <code>index.html</code>, is the entry point to your CMS admin. This is where Netlify CMS lives. You don't need to do styling or anything as it is already done for you with the script tag in the example below:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Content Manager<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The second file, <code>config.yml</code>, is the main core of the Netlify CMS. It's going to be a bit complicated as we are going to write backend code. We'll talk more about it in the configuration section.</p>
<h3 id="heading-how-to-configure-the-back-end">How to configure the back end</h3>
<p>In this guide, we are using Netlify for hosting and authentication and so the backend configuration process should be relatively straightforward. Add all the code snippets in this section to your <code>admin/config.yml</code> file.</p>
<p>We'll begin by adding the following codes:</p>
<pre><code class="lang-yml"><span class="hljs-attr">backend:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">git-gateway</span>
  <span class="hljs-attr">branch:</span> <span class="hljs-string">master</span>
</code></pre>
<p><strong>Heads up</strong>: This code above works for GitHub and GitLab repositories. If you're using Bitbucket to host your repository, follow these <a target="_blank" href="https://www.netlifycms.org/docs/bitbucket-backend/">instructions</a> instead.</p>
<p>The code we just wrote specifies your backend protocol and your publication branch (which is branch: master). Git Gateway is an open-source API that acts as a proxy between authenticated users of your site and your site repository. I'll explain more what this does in the authentication section.</p>
<p>Next up, we will write <code>media_folder: "images/uploads"</code>. This will allow you to add media files like photos directly to your CMS. Then you won't need to use a text editor to manually add media and all that.</p>
<pre><code class="lang-yml"><span class="hljs-attr">media_folder:</span> <span class="hljs-string">"images/uploads"</span>
</code></pre>
<p>Make sure you created a folder called <code>images</code> in the <code>admin</code> folder. Inside the <code>images</code> folder, create an <code>uploads</code> folder as this is the place where you'll host your images.</p>
<h3 id="heading-configure-collections">Configure Collections</h3>
<p>The collections will define the structure for the different content types on your static site. As every site can be different, how you configure the collection's settings will differ from one site to another.</p>
<p>Let's just say your site has a blog, with the posts stored in <code>content/blog</code>, and files saved in a date-title format, like <code>2020-09-26-how-to-make-sandwiches-like-a-pro.md</code>. Each post begins with settings in the YAML-formatted front matter in this way:</p>
<pre><code class="lang-md">---
layout: blog
title: "How to make sandwiches like a pro"
date: 2020-09-26 11:59:59
<span class="hljs-section">thumbnail: "/images/sandwich.jpg"
---</span>

This is the post body where I write about how to make a sandwich so good that will impress Gordon Ramsay.
</code></pre>
<p>With this example above, this is how you will add <code>collections</code> settings to your Netlify CMS <code>config.yml</code> file:</p>
<pre><code class="lang-yml"><span class="hljs-attr">collections:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"blog"</span>
    <span class="hljs-attr">label:</span> <span class="hljs-string">"Blog"</span>
    <span class="hljs-attr">folder:</span> <span class="hljs-string">"content/blog"</span>
    <span class="hljs-attr">create:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">slug:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{year}}</span>-<span class="hljs-template-variable">{{month}}</span>-<span class="hljs-template-variable">{{day}}</span>-<span class="hljs-template-variable">{{slug}}</span>"</span>
    <span class="hljs-attr">fields:</span>
      <span class="hljs-bullet">-</span> {<span class="hljs-attr">label:</span> <span class="hljs-string">"Layout"</span>, <span class="hljs-attr">name:</span> <span class="hljs-string">"layout"</span>, <span class="hljs-attr">widget:</span> <span class="hljs-string">"hidden"</span>, <span class="hljs-attr">default:</span> <span class="hljs-string">"blog"</span>}
      <span class="hljs-bullet">-</span> {<span class="hljs-attr">label:</span> <span class="hljs-string">"Title"</span>, <span class="hljs-attr">name:</span> <span class="hljs-string">"title"</span>, <span class="hljs-attr">widget:</span> <span class="hljs-string">"string"</span>}
      <span class="hljs-bullet">-</span> {<span class="hljs-attr">label:</span> <span class="hljs-string">"Publish Date"</span>, <span class="hljs-attr">name:</span> <span class="hljs-string">"date"</span>, <span class="hljs-attr">widget:</span> <span class="hljs-string">"datetime"</span>}
      <span class="hljs-bullet">-</span> {<span class="hljs-attr">label:</span> <span class="hljs-string">"Body"</span>, <span class="hljs-attr">name:</span> <span class="hljs-string">"body"</span>, <span class="hljs-attr">widget:</span> <span class="hljs-string">"markdown"</span>}
</code></pre>
<p>Let's examine what each of these fields does:</p>
<ul>
<li><code>name</code>:  This one is used in routes like /admin/collections/blog</li>
<li><code>label</code>: This one is used in the UI (User Interface). When you are in the admin page, you will see a big word "Blog" on the top of the screen. That big word "Blog" is the label.</li>
<li><code>folder</code>: This one points to the file path where your blog posts are stored.</li>
<li><code>create</code>: This one lets the user (you or whoever has admin access) create new documents (blog posts in this case) in these collections.</li>
<li><code>slug</code>: This one is the template for filenames. <code>{{year}}</code>, <code>{{month}}</code>, and <code>{{day}}</code> which are pulled from the post's date field or save date. <code>{{slug}}</code> is a URL-safe version of the post's title. By default it is <code>{{slug}}</code>.</li>
</ul>
<p>The fields are where you can customize the content editor (the page where you write the blog post). You can add stuff like ratings (1-5), featured images, meta descriptions, and so on.</p>
<p>For instance, in this particular code, we add curly braces <code>{}</code>. Inside them we write <code>label</code> with the value "Publish Date" which will be the label in the editor UI. </p>
<p>The <code>name</code> field is the name of the field in the front matter and we name it "date" since the purpose of this field is to enter the date input.</p>
<p>And lastly, the widget determines how the UI style will look and the type of data we can enter. In this case, we wrote <code>"datetime"</code> which means we can only enter the date and time.</p>
<pre><code class="lang-yml"><span class="hljs-bullet">-</span> {<span class="hljs-attr">label:</span> <span class="hljs-string">"Publish Date"</span>, <span class="hljs-attr">name:</span> <span class="hljs-string">"date"</span>, <span class="hljs-attr">widget:</span> <span class="hljs-string">"datetime"</span>}
</code></pre>
<p>You can check the list right <a target="_blank" href="https://www.netlifycms.org/docs/widgets/">here</a> to see what exactly you can add. If you want, you can even create your own widgets, too. For the sake of brevity, we'll try to keep things simple here.</p>
<h3 id="heading-enable-authentication">Enable Authentication</h3>
<p>At this point, we are nearly done with the installation and configuration of Netlify CMS. Now it's time to connect your Gatsby site to the CMS by enabling authentication. </p>
<p>We'll add some HTML code and then activate some features from Netlify. After that, you are on the way to creating your first blog post.</p>
<p>We are going to need a way to connect a front end interface to the backend so that we can handle authentication. To do that, add this HTML script tag to two files:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://identity.netlify.com/v1/netlify-identity-widget.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>The first file to add this script tag is the <code>admin/index.html</code> file. Place it between the <code>&lt;head&gt;</code> tags. And the second file to add the tag is the <code>public/index.html</code> file. This one also goes in between the <code>&lt;head&gt;</code> tags.</p>
<p>When a user logs in with the Netlify Identity widget, an access token directs them to the site homepage. In order to complete the login and get back to the CMS, redirect the user back to the <code>/admin/</code> path.</p>
<p>To do this, add the following code before the closing <code>body</code> tag of the <code>public/index.html</code> file:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.netlifyIdentity) {
    <span class="hljs-built_in">window</span>.netlifyIdentity.on(<span class="hljs-string">"init"</span>, <span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (!user) {
        <span class="hljs-built_in">window</span>.netlifyIdentity.on(<span class="hljs-string">"login"</span>, <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">document</span>.location.href = <span class="hljs-string">"/admin/"</span>;
        });
      }
    });
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>With this, we are now done writing the code and it's time to visit Netlify to activate authentication.</p>
<p>Before we move on, you should Git commit your changes and push them to the repository. Plus, you will have to deploy your site live so you can access the features in the Enable Identity and Git Gateway section.</p>
<h2 id="heading-deploy-your-site-live-with-netlify">Deploy your site live with Netlify</h2>
<p>We are going to use Netlify to deploy our Gatsby site live. The deployment process is pretty straightforward, quick, and most importantly, it comes with a free SSL (Secure Sockets Layer). This means your site is protected (you can tell by looking at the green lock on the browser search).</p>
<p>If you haven't signed up for the platform, you can do it <a target="_blank" href="https://app.netlify.com/signup?_ga=2.69477016.986166254.1601369549-1254573554.1571849986">right here</a>. When you've finished signing up, you can begin the deployment process by following these 3 steps.</p>
<ol>
<li>Click the "New site from Git" button to create a new site to be deployed. Choose the Git provider where your site is hosted. My site is hosted on GitHub so that's what I will choose.</li>
<li>Choose the repository you want to connect to Netlify. The name of my Gatsby site is "foodblog" but you have to pick your own project name.</li>
<li>The last one asks how you would like Netlify to adjust your builds and deploy your site. We are going to leave everything as it is and we will click the "Deploy site" button. This will begin deploying your site to live.</li>
</ol>
<p>Once the deployment is complete, you can visit your live site by clicking the green link that has been generated for you on the top left of the screen. Example: <code>https://random_characters.netlify.app</code>.</p>
<p>With this, the world can now view your site. You can replace the weird URL with your custom domain by reading this <a target="_blank" href="https://docs.netlify.com/domains-https/custom-domains/#definitions">documentation</a>.</p>
<h3 id="heading-how-to-enable-identity-and-git-gateway">How to enable Identity and Git Gateway</h3>
<p>Netlify's Identity and Git Gateway services help you manage CMS admin users for your site without needing them to have an account with your Git host (Like GitHub) or commit access on your repository.</p>
<p>To activate these services, head to your site dashboard on Netlify and follow these steps:</p>
<ol>
<li>Go to <strong>Settings</strong> &gt; <strong>Identity</strong>, and select <strong>Enable Identity</strong> service.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--34-.png" alt="Image" width="600" height="400" loading="lazy">
<em>In the Overview page of your site, click the "Settings" link.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--36-.png" alt="Image" width="600" height="400" loading="lazy">
<em>After clicking "Settings", scroll down the left sidebar and click the "Identity" link.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--37-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click the "Enable Identity" button to activate the Identity feature.</em></p>
<ol start="2">
<li>Under <strong>Registration</strong> preferences, select <strong>Open</strong> or <strong>Invite only</strong>. Most of the time, you want only invited users to access your CMS. But if you are just experimenting, you can leave it open for convenience.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--38-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Under the Identity submenu, click the "Registration" link and you'll be taken to the registration preferences.</em></p>
<ol start="3">
<li>Scroll down to <strong>Services</strong> &gt; <strong>Git Gateway</strong>, and click <strong>Enable Git Gateway</strong>. This authenticates with your Git host and generates an API access token. </li>
</ol>
<p>In this case, we're leaving the <strong>Roles</strong> field blank, which means any logged-in user may access the CMS.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--40-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Under the Identity submenu, click the "Services" link.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--41-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click the "Enable Git Gateway" button to activate the Git Gateway feature.</em></p>
<p>With this, your Gatsby site has been connected with Netlify CMS. All that is left is to access the CMS admin and write blog posts.</p>
<h2 id="heading-how-to-access-the-cms">How to access the CMS</h2>
<p>All right, you are now ready to write your first blog post!</p>
<p>There are two ways to access your CMS admin, depending on what accessing options you chose from the Identity.</p>
<p>If you selected <strong>Invite only</strong>, you can invite yourself and other users by clicking the Invite user button. Then an email message will be sent with an invitation link to login to your CMS admin. Click the confirmation link and you'll be taken to the login page.</p>
<p>Alternatively, if you selected <strong>Open</strong>, you can access your site's CMS directly at <code>yoursite.com/admin/</code>. You will be prompted to create a new account. When you submit it, a confirmation link will be sent to your email. Click the confirmation link to complete the signup process and you'll be taken to the CMS page.</p>
<p><strong>Note</strong>: If you cannot access your CMS admin after clicking the link from the email, the solution is to copy the link in the browser starting with <code>#confirmation_token=random_characters</code> and paste the link after the hashtag "#", like this: <code>https://yoursite.com/admin/#confirmation_token=random_characters</code>. This should fix the problem.</p>
<p>If everything goes well, you should see your site's admin dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot--42-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Netlify CMS admin.</em></p>
<p>You can create your new post by clicking the "New post" button.</p>
<p>When you're ready to publish your post, you can click the "Publish Now" button to publish it immediately.</p>
<p>When you hit the publish button, the post file is automatically created. Then it will add to the changes with the commit message based on the name of the post along with the date and time of publishing. Finally, it will be pushed to the host repository, and from there your post will be seen live.</p>
<p>You can view the changes by looking at the commit message in your host repository.</p>
<p>After waiting for a few minutes, your new post should be live.</p>
<h3 id="heading-one-more-thing">One more thing</h3>
<p>The last thing to do is clean up the sample articles. To delete these posts, go to the blog files in your text editor and delete them one by one. Make sure you check your terminal when deleting them so that there will be no issues on your site.</p>
<p>Once all the sample posts are cleared out, commit these changes and push them to the repository.</p>
<p>And now, you are all done! You can now create your new posts from the comfortable CMS dashboard and share your stories to the world.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this guide you have learned how to:</p>
<ul>
<li>Create a Gatsby blog site</li>
<li>Added the Netlify CMS to your Gatsby site by creating and configuring files</li>
<li>Enable user authentication by activating Identity and Git Gateway</li>
<li>Access your site's CMS admin</li>
<li>Publish your first post powered by Gatsby and Netlify CMS</li>
</ul>
<p>By the end of this guide, you should now be able to enjoy writing blog posts with a fast website and simple content editor. And you probably don't have to touch the code unless it needs further customization.</p>
<p>There is still more to cover about Gatsby and Netlify CMS. One of the best ways to learn about them is to go through their documentation.</p>
<p>I hope you found this guide beneficial, and happy posting!</p>
<p><a target="_blank" href="https://www.mohammedasker.com/">Check out my blog</a> to learn more tips, tricks, and tutorials about web development.</p>
<p>Cover photo by <a target="_blank" href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NeONBRAND</a> on <a target="_blank" href="https://unsplash.com/s/photos/blogging?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>.</p>
<h3 id="heading-resources-for-javascript-and-react">Resources for JavaScript and React</h3>
<p>Here are some resources that may help you to learn JavaScript and React:</p>
<p><strong>JavaScript</strong></p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">Official JavaScript Documentation</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/">freeCodeCamp</a></li>
<li><a target="_blank" href="https://www.w3schools.com/js/">W3Schools: JavaScript Tutorial</a></li>
<li><a target="_blank" href="https://javascript.info/">JavaScript.info</a></li>
</ul>
<p><strong>React</strong></p>
<ul>
<li><a target="_blank" href="https://reactjs.org/docs/getting-started.html">Official React Documentation</a></li>
<li><a target="_blank" href="https://www.udemy.com/course/complete-react-developer-zero-to-mastery/">Complete React Developer in 2020 (w/ Redux, Hooks, GraphQL)</a></li>
<li><a target="_blank" href="https://scrimba.com/learn/learnreact">Scrimba: Learn React for free</a></li>
<li><a target="_blank" href="https://flaviocopes.com/page/react-handbook/">Flavio Copes: The React Handbook</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Grow Your Audience and Share Your Content with the World ]]>
                </title>
                <description>
                    <![CDATA[ Building quality content can be a really rewarding task. But for those just starting out or those who don't yet have a large audience, that content can easily get buried amongst the other million tweets on Twitter. How can we expand our reach and hel... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-grow-your-audience-and-share-your-content-with-the-world/</link>
                <guid isPermaLink="false">66b8e35f684cb75ad7f76d1e</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Blogger ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Blogging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #content marketing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ marketing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technical writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ write ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing tips ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Thu, 11 Jun 2020 14:45:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/grow-audience.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building quality content can be a really rewarding task. But for those just starting out or those who don't yet have a large audience, that content can easily get buried amongst the other million tweets on Twitter. How can we expand our reach and help our content impact more people?</p>
<ul>
<li><a class="post-section-overview" href="#heading-creating-our-content-for-the-masses">Creating our content for the masses</a></li>
<li><a class="post-section-overview" href="#heading-what-do-i-mean-by-building-an-audience">What do I mean by building an audience?</a></li>
<li><a class="post-section-overview" href="#heading-leveraging-larger-platforms-and-publications">Leveraging larger platforms and publications</a></li>
<li><a class="post-section-overview" href="#heading-syndicating-with-other-platforms">Syndicating with other platforms</a></li>
<li><a class="post-section-overview" href="#heading-contributing-to-other-publications">Contributing to other publications</a></li>
<li><a class="post-section-overview" href="#heading-sharing-on-content-curation-platforms">Sharing on content curation platforms</a></li>
<li><a class="post-section-overview" href="#heading-other-notable-places">Other notable places</a></li>
</ul>
<h2 id="heading-creating-our-content-for-the-masses">Creating our content for the masses</h2>
<p>Every content creator has been there – you just wrote an article that you're super excited about and think it's going to be a massive hit. But you quickly find that overnight success never came when you wake up hoping to find thousands of likes on the tweet only to find none.</p>
<p>But that might not have anything to do with your article. It could absolutely be groundbreaking, but unfortunately there's not many people around to see it.</p>
<p>Social networks don't tend to prioritize their feeds based on how good your article is, but how much interaction and engagement that post received. While that kind of makes sense, that doesn't play well for people just starting out.</p>
<p>So how do we expand that reach? While the obvious route is to buy your way to the top with ads, I'm going to focus on more natural ways we can build our audience.</p>
<h2 id="heading-what-do-i-mean-by-building-an-audience">What do I mean by building an audience?</h2>
<p>Your favorite web developers or social media influencers didn't start with 100,000 followers – most of them worked really hard to get where they are.  That process is what building an audience is all about.</p>
<p>By creating content, you're working to find others who enjoy your take or get inspired by it. Ideally, those same people will follow you on their favorite platform with the hope that your next piece will inspire them just the same.</p>
<p>Your audience is those people who followed you or are actively engaging with the content you create. The ones who are supporting you by giving a thumbs up to your new video. The ones who subscribe to <a target="_blank" href="https://www.colbyfayock.com/newsletter/">your newsletter</a> because they're eager to see the awesome work you did that week.</p>
<p>By working hard on your content, being consistent, and <a target="_blank" href="https://www.colbyfayock.com/2020/05/how-to-write-more-effectively-and-develop-your-unique-style/">finding your voice</a>, you'll naturally build that audience. But to build that audience, people first need to see it. So how can we get it in front of people?</p>
<h2 id="heading-leveraging-larger-platforms-and-publications">Leveraging larger platforms and publications</h2>
<p>While your new blog might not have anyone subscribed to your RSS feed, there are platforms out there with large audiences that are actively looking for more authors.</p>
<p>By leveraging these audiences, you're helping to get your foot in the door. While you might need to give up a little bit of ownership of the article by writing it somewhere that's not your own blog, you're providing a means for more people to read your content and find out who you are.</p>
<p>This process can take many forms, but I'm going to focus on a few things:</p>
<ul>
<li>Syndicating with other platforms</li>
<li>Contributing to other publications</li>
<li>Sharing on content curation platforms</li>
</ul>
<h2 id="heading-syndicating-with-other-platforms">Syndicating with other platforms</h2>
<p>Syndicating your content can be one of the more effective means of getting access to a larger network while holding on to a lot of the benefits of hosting your own content.</p>
<p>Not all platforms are the same, but most of the ones I'm going to go over allow you to provide a <a target="_blank" href="https://moz.com/learn/seo/canonicalization">canonical link</a> to your own website. What this does is allow you to publish your work on different platforms all while the "SEO juice" ultimately flows back to your own blog.</p>
<p>While it's important from an SEO perspective, it's also generally a good way to keep your content hosted on your own website where you know you'll maintain it for the foreseeable future. If one of those platforms ends up closing down, you still have all of your content safe at home.</p>
<p>So what are some platforms that we can syndicate with and how can you set it up?</p>
<h3 id="heading-devto">Dev.to</h3>
<p><a target="_blank" href="https://dev.to/">Dev.to</a> is the choice platform for developers who want to share their knowledge and open up conversation to the larger tech community. They provide an incredibly welcoming and safe space where developers experienced and new can publish their work.</p>
<p>The great part about it is that everyone seems to get a bit of a fair chance at getting their work out to the world. While they have similar features to other networks where you can follow your favorite authors, newer authors still show up in the content feed giving you a chance to be seen.</p>
<p>To syndicate on dev.to, you'll want to set up the canonical link in the post configuration. When editing the content, you'll want to look for the settings icon, where you'll then be provided with an input where you can add the original URL for your post.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/devto-post-canonical-url.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Adding a canonical URL to a dev.to post</em></p>
<h3 id="heading-medium">Medium</h3>
<p><a target="_blank" href="https://medium.com/">Medium</a> has suffered a lot of criticism lately due to its aggressive tactics and monetization strategies, but it's still an effective platform for newcomers who don't have an audience to get in front of.</p>
<p>Medium's large network of users and content curation makes it a great platform for expanding your reach. But it doesn't stop at posting there.</p>
<p>To be effective with your Medium posts, you want to look to get published with an existing Medium publication. <a target="_blank" href="https://medium.com/better-programming">Better Programming</a> is one of the larger developer publications that are always <a target="_blank" href="https://medium.com/better-programming/write-for-us-5c4bcba59397">looking for new authors</a> to contribute.</p>
<p>Submitting to a Medium publication will help you take advantage of not only Medium's network, but that publication's network that gives you a better chance of getting selected by Medium's curation team and landing in someone's newsletter inbox.</p>
<p>Adding a canonical link to your Medium posts is a bit trickier though. To do this, you must "import" your story rather than copy and pasting it in. The good news, is Medium also tries to import all of the content, ideally making it a bit easier to add (remember to review all of the formatting!).</p>
<p>On your Stories page, you can find the Import button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/medium-import-story.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Importing a story on Medium</em></p>
<p>Once it's imported, you won't really be able to see much, but once you preview the page, you can view the source and find the canonical link added.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/medium-post-source-canonical-link.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Canonical link in Medium post source</em></p>
<h2 id="heading-contributing-to-other-publications">Contributing to other publications</h2>
<p>While you might have to give up ownership of your content, writing for other publications or writing as a "guest post" is a great way to get started and gain some ground where you might not have had any before.</p>
<p>Depending on the publisher, you might not ever get the opportunity to post it on your own website, but you're trading that for the privilege to get your work out to potentially millions of people instead of the 10 unique visitors from the month of May.</p>
<p>This can even open up the door to more opportunities. This helps give your name recognition and authority that can help in your job search or simply another chance to write for the same publisher or a different bigger one.</p>
<p>The good news – is there's an endless supply of big name publishers that you can submit your work to. Here are a few you can get started with:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/developer-news-style-guide/">freeCodeCamp News</a></li>
<li><a target="_blank" href="https://www.smashingmagazine.com/write-for-us/">Smashing Magazine</a></li>
<li><a target="_blank" href="https://css-tricks.com/guest-posting/">CSS-Tricks</a></li>
<li><a target="_blank" href="https://sitepoint.typeform.com/to/DMmYfn">Sitepoint</a></li>
<li><a target="_blank" href="https://envato.formstack.com/forms/create_a_tutorial?Source=&amp;Medium=">Tuts+</a></li>
<li><a target="_blank" href="https://www.webdesignerdepot.com/write-for-us/">Web Designer Depot</a></li>
<li><a target="_blank" href="https://dzone.com/pages/contribute">DZone</a></li>
<li><a target="_blank" href="https://www.pluralsight.com/guides">Pluralsight Guides</a> (See "Teach" in footer)</li>
</ul>
<p>Each of these publications have different processes of getting published. So be patient and keep working hard at your content.</p>
<p><em>Note: know of another publisher that offers writing opportunities? <a target="_blank" href="https://twitter.com/colbyfayock">Let me know</a> and I'll add them above!</em></p>
<h2 id="heading-sharing-on-content-curation-platforms">Sharing on content curation platforms</h2>
<p>It would be great if we all had a massive Twitter following where we could post about our new blog, but that's typically not the case. So while you should absolutely set up a <a target="_blank" href="https://twitter.com/colbyfayock">social media profile</a> and get your content out there, I'll focus on other platforms here.</p>
<h3 id="heading-reddit">Reddit</h3>
<p>Though there are certainly rules about self-promotion depending on the subreddit, <a target="_blank" href="https://www.reddit.com/">Reddit's</a> a massive community of real people who are curious and eager to learn about something new.</p>
<p>Learn about the different tech or <a target="_blank" href="https://www.reddit.com/r/webdev/">webdev</a> subreddits that are out there. Start getting involved with other people's posts. Develop a relationship with the people there and show why your content has value.</p>
<h3 id="heading-hacker-news">Hacker News</h3>
<p><a target="_blank" href="https://news.ycombinator.com/">Hacker News</a> is tough. It's hard to get noticed and on the front page. And even when you get on the front page, there's a good chance you'll get a lot of criticism you never expected to get.</p>
<p>But if you can have thick skin and learn to take the heat should you get noticed, Hacker News can be an incredible way to broadcast to the world. People have turned into <a target="_blank" href="https://www.youtube.com/watch?v=ifq3xhik8tE">overnight successes</a> by ending up on the front page of Hacker News whether they're the ones who posted it there or not.</p>
<h3 id="heading-more-platforms">More platforms</h3>
<p>While those are two of the big ones, there are a ton more. Here are a few more to get you started:</p>
<ul>
<li><a target="_blank" href="https://www.echojs.com/">Echo JS</a></li>
<li><a target="_blank" href="http://www.rubyflow.com/">RubyFlow</a></li>
<li><a target="_blank" href="https://golangnews.com/">Golang News</a></li>
<li><a target="_blank" href="https://www.webdesignernews.com/">Web Designer News</a></li>
</ul>
<h2 id="heading-other-notable-places">Other notable places</h2>
<h3 id="heading-chat-based-communities">Chat-based Communities</h3>
<p>There are a ton of <a target="_blank" href="https://www.freecodecamp.org/news/freecodecamp-discord-chat-room-server/">communities</a> using platforms like <a target="_blank" href="https://slack.com/">Slack</a> or <a target="_blank" href="https://discord.com/">Discord</a> that are <a target="_blank" href="https://www.100daysofcode.com/resources/">incredibly supportive</a> in helping each other grow. While you shouldn't simply spam your content, try to start a conversation around it. Talk about why the topic is important to you or how your tutorial can help others in their code journey.</p>
<h3 id="heading-newsletters">Newsletters</h3>
<p>This is something a lot of people regret not starting earlier – myself included. It's not too much effort to start a newsletter <a target="_blank" href="https://app.convertkit.com/referrals/l/36ce3fce-f231-48b5-b878-e622d0265c3f">with a platform that has a free tier</a> and keep it in your back pocket until you see some growth. No one's going to judge you for waiting a few months to put out content, but once you begin to grow your audience, they'll be excited to see all of your new work.</p>
<h3 id="heading-other-peoples-newsletters">Other people's newsletters</h3>
<p>You don't have to have your own newsletter to get into someone's inbox, there are already a ton of newsletter curators doing a lot of hard work to find great content around the web, but if they don't see it, how can they include it?</p>
<p>Most newsletter publications love to receive submissions. This helps their publication grow and include content from people who might not have a good opportunity on their own.</p>
<p>While you can Google around and find a newsletter that makes sense for you, here are a few that I follow along with that I know would love to see your work:</p>
<ul>
<li>All <a target="_blank" href="https://cooperpress.com/publications/">Cooperpress</a> Publications including <a target="_blank" href="http://javascriptweekly.com/">Javascript Weekly</a>, <a target="_blank" href="https://serverless.email/">Serverless Status</a>, and <a target="_blank" href="https://cooperpress.com/publications/">a bunch more</a></li>
<li><a target="_blank" href="https://webtoolsweekly.com/">Web Tools Weekly</a></li>
<li><a target="_blank" href="https://css-weekly.com/">CSS Weekly</a></li>
</ul>
<h2 id="heading-building-your-brand">Building your "brand"</h2>
<p>"Brand" is a funny word and can come sometimes come with negative connotations. But really as an author, your goal should be to build a presence that you carry with you consistently through all of your different outlets of work.</p>
<p>For instance, when possible, try to always use the same username when creating author profiles. You can find me mostly anywhere at <a target="_blank" href="https://twitter.com/colbyfayock">@colbyfayock</a> – it makes it easy for people to find me on a new network.</p>
<p>Also try to use the same picture. While it's definitely fun to customize your different profiles depending on who's there, the benefit of using the same picture is people will come to recognize you by that picture. They'll instantly be able to remember they saw your blog post when they see your tweet come through their feed.</p>
<h2 id="heading-be-patient-your-time-will-come">Be patient, your time will come</h2>
<p>You're not going to find an overnight success without spending a lot of money, so be patient. Content development is hard, it takes time to both build an audience and figure out your voice.</p>
<p>But chances are, if this is your first article you've ever written, maybe that first publisher you sent it to wasn't interested in it.</p>
<p><strong>THAT'S OKAY!</strong> Don't give up! Send it to another publisher and try the original one for your next article. When I first started out, it took a few articles before I got accepted as an author to freeCodeCamp's Medium publication.</p>
<p>The more you write, the more <a target="_blank" href="https://www.colbyfayock.com/2020/05/how-to-write-more-effectively-and-develop-your-unique-style/">you'll learn about what's effective</a>. It's not a sprint – be patient and just keep up the hard work.</p>
<h2 id="heading-overcoming-the-fear-of-sharing-your-work">Overcoming the fear of sharing your work</h2>
<p>Most of these things aren't easy to do. The idea that you might have to face critical feedback of your work is absolutely scary!</p>
<p>But <a target="_blank" href="https://www.colbyfayock.com/2020/04/overcoming-your-fear-of-writing-and-how-you-can-find-motivation/">overcoming this fear</a> is an important step of the process. I personally struggled with this when I began writing, but the more you put yourself our there, the less scary it becomes and you'll quickly notice there aren't as many bad people out there looking to say bad things about your work.</p>
<p>Instead, you're opening yourself up to an opportunity to learn and grow. Though not everyone's the same, the tech community can be overwhelmingly welcoming and supportive. The more you share and put yourself out there, the more receptive people will be to wanting to hear what you have to say.</p>
<h2 id="heading-how-do-you-share-your-work">How do you share your work?</h2>
<p><a target="_blank" href="https://twitter.com/colbyfayock">Let me know on Twitter!</a></p>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
