<?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[ Ghost - 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[ Ghost - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 06 May 2026 17:00:32 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/ghost-tag/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Add a Table of Contents to Your Article on Ghost ]]>
                </title>
                <description>
                    <![CDATA[ A table of contents (or TOC) is a list of chapters or section titles in an article. You'll usually find it at the beginning of an article, right after the introduction. When you write a long blog post or an article with independent sections, consider... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-a-table-of-contents-to-your-article-on-ghost/</link>
                <guid isPermaLink="false">66ba11df64fe3b6039f992a8</guid>
                
                    <category>
                        <![CDATA[ Ghost ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technical writing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayu Adiati ]]>
                </dc:creator>
                <pubDate>Fri, 01 Dec 2023 19:47:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/11/How-to-Add-a-Table-of-Contents-to-Your-Article-on-Ghost-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A table of contents (or TOC) is a list of chapters or section titles in an article. You'll usually find it at the beginning of an article, right after the introduction.</p>
<p>When you write a long blog post or an article with independent sections, consider adding a table of contents. It can help your readers understand your article's content and know what's coming. Providing hyperlinks to each title in the table of contents helps readers navigate between the sections they want to check out.</p>
<p>Having a table of contents in your articles also benefits you as a writer. First, you can easily see the flow of your article. Another benefit is that it can increase your blog's traffic. If your readers have a great experience reading your article, they will likely bookmark and return to it.</p>
<p>Some publications use <a target="_blank" href="https://ghost.org/">Ghost</a> to write and publish articles on their website, and freeCodeCamp is one of them. In this article, I will walk you through how to add a clickable table of contents to your article on Ghost.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
    <li><a href="#understanding-headings">Understanding Headings</a></li>
    <li><a href="#how-to-add-a-table-of-contents">How to Add a Table of Contents</a>
        <ul>
            <li><a href="#step-1-create-a-list-of-headings">Step #1 - Create a List of Headings</a>
                <ul>
                    <li><a href="#create-a-list-of-one-level-headings">Create a List of One-Level Headings</a></li>
                    <li><a href="#create-a-list-with-subheadings">Create a List with Subheadings</a></li>
                </ul>
            </li>
            <li><a href="#step-2-inspect-a-heading-and-find-the-id">Step #2 - Inspect a Heading and Find the <code>id</code></a></li>
            <li><a href="#step-3-add-the-link">Step #3 - Add the Link</a>
                <ul>
                    <li><a href="#add-a-link-to-a-heading">Add a Link to a Heading</a></li>
                    <li><a href="#add-a-link-to-a-heading-in-html">Add a Link to a Heading in HTML</a></li>
                    <li><a href="#why-does-the-link-not-work">Why Does the Link Not Work?</a></li>
                </ul>
            </li>
        </ul>
    </li>
    <li><a href="#final-words">Final Words</a></li>
</ul>

<h2 id="heading-understanding-headings">Understanding Headings</h2>
<p>Before we jump into how to add a table of contents, let's take a moment to understand "headings" because I will use this term going forward.</p>
<blockquote>
<p>Headings are titles of sections and subsections that are displayed on a website.</p>
</blockquote>
<p>There are six levels of headings based on the importance of the section.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/h1-h6.png" alt="Image" width="600" height="400" loading="lazy">
<em>Levels of headings – H1 - H6</em></p>
<p>Following best practices, a page on a website should only have one <code>heading level 1</code> (known as <code>h1</code>). It is the main title of the page. In your case, it'll be the main title of your article. (In this article, it's "How to Add a Table of Contents to Your Article on Ghost".)</p>
<p>So, writing an article on Ghost (or any other publication platform) leaves you with <code>h2</code> to <code>h6</code> for your section and subsection titles. And you can use as many of them as you need depending on the hierarchy and organization of your content.</p>
<h2 id="heading-how-to-add-a-table-of-contents">How to Add a Table of Contents</h2>
<p>You can create your table of contents whenever you want – before you write, during the process, or after you finish writing your article. </p>
<p>But you can only add the link to each chapter or section title after writing your article. This is because you'll need to know exactly how the text of the headings will appear in order to link to them properly, as you'll see below.</p>
<p>In this guide, I'll show you how to build a ToC in HTML, which allows you to add subheadings in addition to main headings/section titles. </p>
<h3 id="heading-step-1-create-a-list-of-headings">Step #1 - Create a List of Headings</h3>
<p>You can create a table of contents with a <a class="post-section-overview" href="#heading-create-a-list-of-one-level-headings-2">list of one-level headings</a> (<code>h2</code>) or <a class="post-section-overview" href="#heading-create-a-list-with-subheadings-2">include the subheadings</a> (<code>h3</code> to <code>h6</code>) if you want to.</p>
<p>You can also choose how you want to format your list:</p>
<ul>
<li><strong>Unordered List</strong>:  A list with bullet points.</li>
<li><strong>Ordered List</strong>: A list with numbers.</li>
</ul>
<h4 id="heading-create-a-list-of-one-level-headings">Create a List of One-Level Headings</h4>
<p>Follow these steps to create a one-level headings list:</p>
<ol>
<li>Insert a new line.</li>
<li>Type a dash (<code>-</code>) or an asterisk (<code>*</code>) for the unordered list. Or, type a number and add a period — for example, <code>1.</code> — for the ordered list.</li>
<li>Hit the spacebar.</li>
<li>Add the title.</li>
<li>Click enter to add a new item in the list.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/add-list-ghost.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Add an unordered and an ordered list.</em></p>
<p><strong>Note</strong>: You can skip the next section and go directly to "<a class="post-section-overview" href="#heading-step-2-inspect-a-heading-and-find-the-id-1">Step #2 - Inspect a Heading and Find the <code>id</code></a>" if you want.</p>
<h4 id="heading-create-a-list-with-subheadings">Create a List with Subheadings</h4>
<p>If you wish to create a list with subheadings (nested list), you must use HTML to format it manually, as Ghost doesn't have nested list options built into the editor.</p>
<p>First, you need to add the HTML card:</p>
<ol>
<li>Start on a new line.</li>
<li>When your cursor is on the new line, you will see a plus (<code>+</code>) icon button in the circle on the left. Click this button.</li>
<li>Click the "HTML" option.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ghost-primary-option-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A plus sign and an "HTML" option on Ghost.</em></p>
<p>Afterward, you will see the HTML card, as shown in the image below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ghost----html-card.png" alt="Image" width="600" height="400" loading="lazy">
<em>HTML card on Ghost.</em></p>
<p>Now, let's start adding the list of your headings and subheadings.</p>
<p>Add an HTML unordered list (<code>&lt;ul&gt;&lt;/ul&gt;</code>) or an ordered list (<code>&lt;ol&gt;&lt;/ol&gt;</code>) element.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

or

<span class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
</code></pre>
<p>To add the list of your headings, add a space between the <code>ul</code> or <code>ol</code> tags. Put the cursor between the tags and hit enter twice.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>Then, add list elements (<code>&lt;li&gt;&lt;/li&gt;</code>) in the empty line between the opening and closing <code>ul</code> or <code>ol</code> tags. Add these list elements based on the number of your section titles.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</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">li</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">li</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">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>Afterward, put each of the titles between the <code>li</code> tags.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 1<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 3<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>
</code></pre>
<p>To add subheadings, repeat all the steps. But now, you should put the <code>ul</code> or <code>ol</code> element inside the <code>li</code> element(s) that hold the section title. Here's how to do it:</p>
<ol>
<li>Place your cursor between the end of the section title and the <code>&lt;/li&gt;</code> tag, then click enter.</li>
<li>Click enter again to add a space and add an <code>ul</code> or <code>ol</code> element for the subheadings.</li>
<li>Add an <code>ul</code> or <code>ol</code> element in the new line, put the cursor between the tags, and hit enter twice to add a space.</li>
<li>Add <code>li</code> element(s) in the new line between the <code>ul</code> or <code>ol</code> tags.</li>
<li>Put the subsection title between the <code>li</code> tags.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/walkthrough-nested-list.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a nested list in HTML.</em></p>
<p>Below is an example of a table of contents with subheadings in HTML.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 1
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 3<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 3<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">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 2
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 3
                <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 4<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">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">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Heading 2 - Part 3<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>
</code></pre>
<p>And the result will look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ghost----HTML-result.png" alt="Image" width="600" height="400" loading="lazy">
<em>A list of section and subsection titles.</em></p>
<h3 id="heading-step-2-inspect-a-heading-and-find-the-id">Step #2 - Inspect a Heading and Find the <code>id</code></h3>
<p>After creating the list, it's time to add the links to the titles in your table of contents.</p>
<p>But first, you need to find the <code>id</code> attribute of your heading using preview mode by inspecting the title with your browser's (Chrome or Firefox) developer tools.</p>
<p>So, let's open your preview mode:</p>
<ol>
<li>Click the gear icon on the top right of your Ghost editor.</li>
<li>Click the "Preview" link beside the "Post URL" input.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ghost-preview-link.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "Preview" link is in the "Post settings" on Ghost.</em></p>
<p>Now that you are in the preview mode, right-click your mouse. You will see some options. Click the "Inspect" option to open the developer tools.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/developer-tools-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>"Inspect" option on Chrome.</em></p>
<p>In the developer tools, let's click the button with a box and arrow icon on the top left to select a heading element on the page and inspect it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/select-element-tool.png" alt="Image" width="600" height="400" loading="lazy">
<em>"Select an element in the page to inspect it" button in the developer tools on Chrome.</em></p>
<p>Hover over a title and click on it. It will highlight the element you want to inspect in the developer tools. Inside the heading element (<code>h2</code> to <code>h6</code>), you will see an <code>id</code> (shown with a red line in the image below).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/inspect-elelemet-devtool.png" alt="Image" width="600" height="400" loading="lazy">
<em>Inspect a heading with developer tools on Chrome.</em></p>
<p>Double-click on the value inside the quote marks of the <code>id</code> and copy it. You will use it to add the link to the title in the table of contents.</p>
<p><strong>Note</strong>: </p>
<ul>
<li>If you <a class="post-section-overview" href="#heading-create-a-list-of-one-level-headings-2">created a list of one-level headings</a>, head to the "<a class="post-section-overview" href="#heading-add-a-link-to-a-heading-1">Add a Link to a Heading</a>" section.</li>
<li>If you <a class="post-section-overview" href="#heading-create-a-list-with-subheadings-2">made a list with subheadings</a>, go to the "<a class="post-section-overview" href="#heading-add-a-link-to-a-heading-in-html-1">Add a Link to a Heading in HTML</a>" section.</li>
</ul>
<h3 id="heading-step-3-add-the-link">Step #3 - Add the Link</h3>
<h4 id="heading-add-a-link-to-a-heading">Add a Link to a Heading</h4>
<p>To add the link, return to your table of contents in the Ghost editor. Then, follow these steps:</p>
<ol>
<li>Highlight the title that you want to add the link to.</li>
<li>Click the chain icon.</li>
<li>Type a hashtag (<code>#</code>) in the input field and paste your copied <code>id</code> value.</li>
</ol>
<p>So, it will be like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ghost-highlight-title.png" alt="Image" width="600" height="400" loading="lazy">
<em>Highlighting a list and a chain icon on Ghost.</em></p>
<pre><code class="lang-markdown"><span class="hljs-section">#understanding-headings</span>
</code></pre>
<p>After you refresh the preview page, check if the link works. It should take you to the target section when you click the title's link. If not, read the "<a class="post-section-overview" href="#heading-why-does-the-link-not-work-1">Why Does the Link Not Work?</a>" section.</p>
<h4 id="heading-add-a-link-to-a-heading-in-html">Add a Link to a Heading in HTML</h4>
<p>First, let's return to your Ghost editor and go to the <code>li</code> element that holds the title in the table of contents.</p>
<p>Then, wrap the title inside an anchor element (<code>&lt;a&gt;&lt;/a&gt;</code>) and add <code>href=""</code> attribute in the opening tag.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</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">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">""</span>&gt;</span>Understanding Headings<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>
</code></pre>
<p>The anchor element creates a hyperlink, and the <code>href</code> attribute is the placeholder for the link's destination.</p>
<p>Now, you will add the link's destination. Type a hashtag (<code>#</code>) and paste the <code>id</code> value you've copied inside the double quotes of the <code>href</code>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</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">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#understanding-headings"</span>&gt;</span>Understanding Headings<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>
</code></pre>
<p>Repeat all the steps to add a link for each title.</p>
<p>Below is an example of section and subsection titles with links in HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</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">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#understanding-headings"</span>&gt;</span>Understanding Headings<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">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#adding-a-table-of-content"</span>&gt;</span>Adding a Table of Content<span class="hljs-tag">&lt;/<span class="hljs-name">a</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">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#step-1-create-a-list-of-titles"</span>&gt;</span>Step #1 - Create a List of Titles<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">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>After refreshing the preview page, you can now check if the link works by clicking it. If it doesn't work, read the next section.</p>
<h4 id="heading-why-does-the-link-not-work">Why Does the Link Not Work?</h4>
<p>If you click a link in your table of contents and it goes nowhere or redirects you to a "404 Page not found", return to the preview page. Refresh it, and try to click the link again.</p>
<p>If it still does the same, you need to check the link in the Ghost editor.</p>
<p>Sometimes, it can be a typo where you change the title but still need to update the link. Or there is a missing hashtag (<code>#</code>) in the link.</p>
<h2 id="heading-final-words"><strong>Final Words</strong></h2>
<p>If you liked and found this article helpful, please share it with others. You can find other works on my <a target="_blank" href="https://adiati.com/">blog</a>. Let's connect on <a target="_blank" href="https://twitter.com/@AdiatiAyu">X (formerly Twitter)</a> or <a target="_blank" href="https://www.linkedin.com/in/adiatiayu/">LinkedIn</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 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 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>
        
    </channel>
</rss>
