Setting up a blog with Shipixen at a later time

Updated on

In some cases, you might turn off the blog in the Shipixen app when you generate your template. However, we can't always predict the future. In those cases when you want to add a blog later, we got you covered!

Overview of the process

The process will require 3 steps to complete:

  1. Add blog overview page & pagination
  2. Add the blog tags pages
  3. Add a default author for blog posts
  4. Finally: add your first blog post

There are few optional steps we'll cover in point 5.:

  • Add blog list to the homepage (or any other page)
  • Add a link to the blog in the navigation
  • Add a link to the blog in the footer

1. Add blog overview page & pagination

Start by creating a new directory under app called all-articles. This will be the overview page for all your blog posts.

Inside this the all-articles directory, create two files

app/all-articles/page.tsx
import ListLayout from '@/layouts/ListLayoutWithTags';
import { allCoreContent, sortPosts } from '@shipixen/pliny/utils/contentlayer';
import { allBlogs } from 'shipixen-contentlayer/generated';
import { genPageMetadata } from 'app/seo';

const POSTS_PER_PAGE = 5;

export const metadata = genPageMetadata({ title: 'Blog' });

export default function BlogPage() {
  const posts = allCoreContent(sortPosts(allBlogs));
  const pageNumber = 1;
  const initialDisplayPosts = posts.slice(
    POSTS_PER_PAGE * (pageNumber - 1),
    POSTS_PER_PAGE * pageNumber,
  );
  const pagination = {
    currentPage: pageNumber,
    totalPages: Math.ceil(posts.length / POSTS_PER_PAGE),
  };

  return (
    <ListLayout
      posts={posts}
      initialDisplayPosts={initialDisplayPosts}
      pagination={pagination}
      title="All Articles"
    />
  );
}
app/all-articles/page/[page].tsx
/* This makes pagination work */
import ListLayout from '@/layouts/ListLayoutWithTags';
import { allCoreContent, sortPosts } from '@shipixen/pliny/utils/contentlayer';
import { allBlogs } from 'shipixen-contentlayer/generated';

const POSTS_PER_PAGE = 5;

export const generateStaticParams = async () => {
  const totalPages = Math.ceil(allBlogs.length / POSTS_PER_PAGE);
  const paths = Array.from({ length: totalPages }, (_, i) => ({
    page: (i + 1).toString(),
  }));

  return paths;
};

export default function Page({ params }: { params: { page: string } }) {
  const posts = allCoreContent(sortPosts(allBlogs));
  const pageNumber = parseInt(params.page as string);
  const initialDisplayPosts = posts.slice(
    POSTS_PER_PAGE * (pageNumber - 1),
    POSTS_PER_PAGE * pageNumber,
  );
  const pagination = {
    currentPage: pageNumber,
    totalPages: Math.ceil(posts.length / POSTS_PER_PAGE),
  };

  return (
    <ListLayout
      posts={posts}
      initialDisplayPosts={initialDisplayPosts}
      pagination={pagination}
      title="All Posts"
    />
  );
}

If you don't want to use all-articles as your path, you can change it to anything you want. However, the configuration needs to match the value of allArticlesPath in data/config/site.settings.ts. Read more in the configuration docs.

2. Add the blog tags pages

Next, we will add the tags pages. This will allow you to filter your blog posts by tags. Tags are also paginated.

Start by creating a new directory under app called tags. This will be the overview page for all your blog posts.

Inside this the tags directory, create two files

app/tags/page.tsx
import Link from '@/components/shared/Link';
import Tag from '@/components/shared/Tag';
import { slug } from 'github-slugger';
import tagData from 'app/tag-data.json';
import { genPageMetadata } from 'app/seo';

export const metadata = genPageMetadata({
  title: 'Tags',
  description: 'All tags on the site.',
});

export default async function Page() {
  const tagCounts = tagData as Record<string, number>;
  const tagKeys = Object.keys(tagCounts);
  const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a]);
  return (
    <>
      <div className="flex flex-col items-start justify-start divide-y divide-gray-200 dark:divide-gray-700 md:mt-24 md:flex-row md:items-center md:justify-center md:space-x-6 md:divide-y-0">
        <div className="space-x-2 pb-8 pt-6 md:space-y-5">
          <h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:border-r-2 md:px-6 md:text-6xl md:leading-14">
            Tags
          </h1>
        </div>
        <div className="flex max-w-lg flex-wrap">
          {tagKeys.length === 0 && 'No tags found.'}
          {sortedTags.map((t) => {
            return (
              <div key={t} className="mb-2 mr-5 mt-2">
                <Tag text={t} />
                <Link
                  href={`/tags/${slug(t)}`}
                  className="-ml-2 text-sm font-semibold uppercase text-gray-600 dark:text-gray-300"
                  aria-label={`View posts tagged ${t}`}
                >
                  {` (${tagCounts[t]})`}
                </Link>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}
app/tags/page/[tag].tsx
import { slug } from 'github-slugger';
import { allCoreContent } from '@shipixen/pliny/utils/contentlayer';
import { siteConfig } from '@/data/config/site.settings';
import ListLayout from '@/layouts/ListLayoutWithTags';
import { allBlogs } from 'shipixen-contentlayer/generated';
import tagData from 'app/tag-data.json';
import { genPageMetadata } from 'app/seo';
import { Metadata } from 'next';

export async function generateMetadata({
  params,
}: {
  params: { tag: string };
}): Promise<Metadata> {
  const tag = decodeURI(params.tag);
  return genPageMetadata({
    title: tag,
    description: `${siteConfig.title} ${tag} tagged content`,
    alternates: {
      canonical: './',
      types: {
        'application/rss+xml': `${siteConfig.siteUrl}/tags/${tag}/feed.xml`,
      },
    },
  });
}

export const generateStaticParams = async () => {
  const tagCounts = tagData as Record<string, number>;
  const tagKeys = Object.keys(tagCounts);
  const paths = tagKeys.map((tag) => ({
    tag: tag,
  }));
  return paths;
};

export default function TagPage({ params }: { params: { tag: string } }) {
  const tag = decodeURI(params.tag);
  // Capitalize first letter and convert space to dash
  const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1);
  const filteredPosts = allCoreContent(
    allBlogs.filter(
      (post) => post.tags && post.tags.map((t) => slug(t)).includes(tag),
    ),
  );
  return <ListLayout posts={filteredPosts} title={`${title} posts`} />;
}

3. Add a default author for blog posts

In order to write blog posts, you need to have an author. It's possible to add multiple authors, but for now, we will just add one that will be used by default when creating new posts.

Create a new file under data/authors called default.md with the following content:

data/authors/default.md
---
name: Jane Doe
avatar: /static/images/avatar.png
occupation: Writer
---

Jane works tirelessly to make sure all customers are happy. We are always looking for ways to improve our service and welcome any feedback.

4. Finally: add your first blog post

Now that we have all the pages and configuration in place, we can add our first blog post.

Adding an article to the blog is as simple as creating a Markdown (mdx file) in the data directory. The file name is used as the slug for the article.

Create a new file under data called my-first-post.mdx with the following content:

data/my-first-post.mdx
---
title: Writing in Markdown
date: 2023-01-01
tags:
    - guide
    - markdown
    - syntax-highlight
summary: Example of a blog post
---

# Introduction

Writing in markdown is easy. You can use any editor you want. We recommend [Visual Studio Code](https://code.visualstudio.com/).

## Basic formatting
You can use **bold**, _italic_ and `code` formatting.

Congratulations! 🎉 You have now added your first blog post.

If you have your website running locally, you should restart the development server and you will see your new blog post (use npm run dev to start the development server after you shut it down).

You can read more on writing markdown in the documentation.

5. Optional steps

While these steps are optional, they are recommended to make your blog more accessible.

5.1 Add blog list to the homepage (or any other page)

If you'd like to show a list of blog posts on the homepage or any other page, you can do so by adding the following code to the page:

app/page.tsx
+ import LatestArticles from '@/components/blog/LatestArticles';

export default function Page() {
...
  return (
     ...
+     <section className="wide-container mt-12">
+       <LatestArticles />
+     </section>
     ...
  );
}

It's likely a good idea to add a link to the blog in the navigation. You can do so by adding the following code to the header navigation links:

data/config/headerNavLinks.ts
+ import { siteConfig } from '@/data/config/site.settings';

export const headerNavLinks = [
  ...
+  { href: siteConfig.allArticlesPath, title: 'Blog' }
];

Similarly, you can add a link to the blog in the footer by adding the following code to the footer navigation links:

data/config/footerLinks.ts
+ import { siteConfig } from '@/data/config/site.settings';

export const footerLinks = [
  {
    columnName: 'Product',
    links: [
      ...
+     { href: siteConfig.allArticlesPath, title: 'Blog' }
    ],
  },
  ...
];