Production grade Next.js

Dynamic routes and content

Dynamic content / Dynamic route / Static page

Next.js makes it easy to create dynamic routes that have params. These routes can still render static pages as well. Later on, we'll learn how to dynamically static generate these routes at run time 👀. So in our Known app, we have a blog. Right now this blog is broken at best. Lets use our CMS to get our blog working.

Static props

So I've been preaching that we can pull from any source to get data for our pages, did you know that includes the file system too? Yes, the filesystem. Next.js will eliminate all this server side code for the browser bundle which makes this possible. It's really magical. First, lets pull in all the blog post on the blog index page. We have two sources of blog posts though. Our CMS and our file system. I don't ever recommend doing that, this setups if for teaching purposes.

./pages/blog/index.tsx
// fs in a react file? wooooow
import fs from 'fs'
import matter from 'gray-matter'
import path from 'path'
import orderby from 'lodash.orderby'
import { useRouter } from 'next/router'
import { posts as postsFromCMS } from '../../content'

const BlogPost = () => {
  // component
  const router = useRouter()

  if (router.isFallback) {
    return (
      <Pane width="100%" height="100%">
        <Spinner size={48} />
      </Pane>
    )
  }
  //
}

// at the bottom
export async function getStaticPaths() {
  const postsDirectory = path.join(process.cwd(), 'posts')
  const filenames = fs.readdirSync(postsDirectory)
  const paths = filenames.map((name) => ({ params: { slug: name.replace('.mdx', '') } }))
  // dont get paths for cms posts, instead, let fallback handle it
  return { paths, fallback: true }
}

export async function getStaticProps(ctx) {
  // read the posts dir from the fs
  const postsDirectory = path.join(process.cwd(), 'posts')
  const filenames = fs.readdirSync(postsDirectory)
  // get each post from the fs
  const filePosts = filenames.map((filename) => {
    const filePath = path.join(postsDirectory, filename)
    return fs.readFileSync(filePath, 'utf8')
  })
  
  // merge our posts from our CMS and fs then sort by pub date
  const posts = orderby(
    [...postsFromCMS.published, ...filePosts].map((content) => {
     // extract frontmatter from markdown content
      const { data } = matter(content)
      return data
    }),
    ['publishedOn'],
    ['desc'],
  )

  return { props: { posts } }
}

We must use getStaticPaths with any page that has a parameter. All we're doing here is reading all the post from the file system and returning all the slugs. We're purposefully not getting the slugs for the posts from the CMS. That could take a long time depending, on how many we have there. So instead, we're going to use fallback: true. This prevents a 404 to a page that has not be statically rendered at build time, and then fetches that page on demand only to statically generate and cache it for future request. Your page will be notified when this happens so you can show a loading screen if you'd like. Its basically on demand static generation. If you're not a fan of the loading screen while your page builds, you can use fallback: blocking that does the same thing, except, the router won't transition until the page is rendered. Very much like getServerSideProps. The flexibility here is amazing. As you'll see in a later lesson, we can even query a database to get our data without hitting an API.

Hit save and you should see a few posts at /blog now. Next we'll fix the blog/[slug] route.