Next.JS Part 6: Dynamic Routes

The final article in this six-part series centers on the routing features in NEXT.js. Employing routing for displaying a single post enables URLs to be dynamically generated for each new post created while maintaining a single base file for displaying the content.

aerial photo of traffic moving on intersecting highways
Photo by Jared Murray @ Unsplash https://unsplash.com/es/@jaredmurray

Here is a recap of the steps covered so far in building a static NEXT.js web site:

This last post will walk through rendering a post in a dedicated page. This will cover dynamic routing in NEXT.js as well as how to convert the markdown content created for each post into HTML to be displayed on the page.

In order to convert the markdown content in posts to HTML we will be leveraging the following:

  • unified processes the source content from your post files.
  • remark-parse converts the source provided by unified and remark-rehype converts the parsed result into HTML.
  • rehype-santize removes any HTML content considered unsafe (ex. scripting) from the converted HTML.
  • rehype-stringify serializes the final HTML output.

From the terminal window in VSCode (Visual Studio Code) enter the following command:

npm install unified remark-parse remark-rehype rehype-sanitize rehype-stringify

From the VSCode Explorer create a new file, lib/mdConterter.ts.

Open mdConverter.ts file and add the following content, then save the file:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeSanitize from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';

export default async function markdownToHtml(markdown: string) {
    
    const result = await unified()
        .use(remarkParse)
        .use(remarkRehype)
        .use(rehypeSanitize)
        .use(rehypeStringify)
        .process(markdown);

    const r = result.toString();

    return r;
};

In VSCode (Visual Stuidio Code) Explorer window, add a new folder called posts under the pages directory. Once you have created the new folder, add a file called [slug].tsx - note the brackets on the file name. The bracket syntax tells NEXT.js you will be providing the slug as a parameter to the new page - this is known as a dynamic route.

Open [slug].tsx and add the following content:

import ErrorPage from 'next/error';
import { useRouter } from 'next/router';
import Head from 'next/head';

import Post from '../../types/post';

import Container from '../../components/container';
import Layout from '../../components/layout';

import { SITE_NAME } from '../../lib/constants';
import { getPost, getAllPosts } from '../../lib/api';
import markdownToHtml from '../../lib/mdConverter';

type Props = {
  post: Post
};

export default function PostView({ post }: Props) {
  
  const router = useRouter()

  if (!router.isFallback && !post?.slug) {
    return <ErrorPage statusCode={404} />
  }

  return (
    <>
      <Layout>
        <Head>
          <title>{SITE_NAME}</title>
        </Head>
        <Container>
          <div className="container mx-auto px-4 py-2 w-10/12 shadow-md lg:mt-2 bg-mysite-light opacity-95 rounded-lg">
           { router.isFallback ? (
              <div>Loading…</div>
            ) : (
              <>
                <div className='mt-4 p-4'>
                  <div className="w-10/12 mx-auto">
                    <h2 className='text-xl font-bold text-mysite-dark mb-1'>{post.title}</h2>
                    <p className='text-sm font-semibold text-mysite-darkaccent mb-2'>{ post.created } by { post.author }</p>
                    <div id="div-post-content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
                  </div>
                </div>
              </>
            )}
          </div>
        </Container>
      </Layout>
    </>
  )
}

type Params = {
  params: {
    slug: string
  }
};

export async function getStaticProps({ params }: Params) {
  const post = getPost(params.slug, [
    'slug',
    'title',
    'created',
    'author',
    'content',
  ])

    const content = await markdownToHtml(post.content || '')

  return {
    props: {
      post: {
        ...post,
        content,
      },
    },
  }
};

export async function getStaticPaths() {
  const posts = getAllPosts(['slug'])

  return {
    paths: posts.map((post) => {
      return {
        params: {
          slug: post.slug,
        },
      }
    }),
    fallback: 'blocking',
  }
};

From the VSCode terminal window enter npm run dev. Open a web browser and navigate to http://localhost:3000/posts/first-post