BLOG
ARTICLE

Sitemap with Local Markdown Files in Next.Js

3 min read

Background

Next.Js has a lot of cool features, but if there's something I find lacking is that making a sitemap for your website is not as straight as I thought it would be.

Here are my specific conditions:

  1. I'm using Vercel to host my website
  2. I have local Markdown files as my blog posts (so no fetching API)

I decided to follow the 'sitemap under pages' approach instead of creating it as static via webpack build. In my other website I used Next.Js's getInitialProps to build xml for sitemap. So for this website I thought similar approach would work fine. This time I used getServerSideProps. It worked in local environment via vercel dev, but not when I deployed to Vercel.

It gave me this error:

1
2
ERROR	Unhandled error during request: 
Error: ENOENT: no such file or directory, scandir '/var/task/posts'

In hindsight, I should have had realized what went wrong. But as I was oblivious, I could not see what the problem was. Only after I found this post, I realized what went wrong:

Vercel was complaining it could not find the directory posts where I store my .md files.

In a javascript file that I use to read .md files as blog posts, initially I used process.cwd() as per instruction in Next.Js blog starter.

1
const postsDirectory = path.join(process.cwd(), 'posts');

To make Vercel include posts directory into source output during deployment, I needed to do:

1
const postsDirectory = path.resolve('./', 'posts');

So that the directory could be found!

The Solution

Here's my sitemap: https://jerfareza.me/sitemap

You can find the final solution below:

pages/sitemap.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { getAllPostsSortDescending, getTagsPaths } from 'lib/api';

const rootUrl = 'https://jerfareza.me';

const createSitemap = (
    posts,
    tags
) => `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        <url>
            <static pages here>
        </url>
        ${posts.map((post) => {
            return `
                <url>
                    <loc>${rootUrl}/blog/${post.slug}</loc>
                    <lastmod>${post.date}</lastmod>
                    <changefreq>daily</changefreq>
                    <priority>0.7</priority>
                </url>
            `;
        }).join('')}
        ${tags.map((tag) => {
            return `
                <url>
                    <loc>${rootUrl}${tag}</loc>
                    <changefreq>daily</changefreq>
                    <priority>0.5</priority>
                </url>
            `;
        }).join('')}
    </urlset>
    `;

export async function getServerSideProps({ res }) {
    const posts = await getAllPostsSortDescending([
        'date',
        'slug',
    ]);
    const tags = await getTagsPaths();

    res.setHeader('Content-Type', 'text/xml');
    res.write(createSitemap(posts, tags));
    res.end();
    return {
        props: {}, // will be passed to the page component as props
    };
}

const Sitemap = () => {
    return <div></div>;
};

export default Sitemap;

lib/api.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.resolve('./', 'posts');

export function getSlugs() {
    return fs.readdirSync(postsDirectory).map(d => d.replace(/\.md$/, ''));
}

export async function getPostBySlug(slug, fields = []) {
    const file = path.join(postsDirectory, `${slug}.md`);
    const fileContents = fs.readFileSync(file, 'utf8');
    const { data, content } = matter(fileContents);

    const items = {};

    fields.forEach((field) => {
        if (field == 'slug') {
            items[field] = slug;
        }
        if (field == 'content') {
            items[field] = content.toString();
        }
        if (data[field]) {
            items[field] = data[field];
        }
    })

    return items;
}

<content redacted>
Jerfareza Daviano

Hi, I'm Jerfareza
Daviano 👋🏼

Hi, I'm Jerfareza Daviano 👋🏼

I'm a Full Stack Developer from Indonesia currently based in Japan.

Passionate in software development, I write my thoughts and experiments into this personal blog.