Skip to content

Commit

Permalink
Feat/sub-route (timlrx#38)
Browse files Browse the repository at this point in the history
* blog subroute support
* docs: update readme and blog

Co-authored-by: mrhut10 <[email protected]>
  • Loading branch information
timlrx and mrhut10 authored May 8, 2021
1 parent 69a4193 commit 735c954
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 119 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public/sitemap.xml
# production
/build
*.xml
# rss feed
/public/index.xml

# misc
.DS_Store
Expand Down
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- Automatic image optimization via [next/image](https://nextjs.org/docs/basic-features/image-optimization)
- Flexible data retrieval with [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote)
- Support for tags - each unique tag will be its own page
- Support for nested routing of blog posts
- Projects page
- SEO friendly with RSS feed, sitemaps and more!

Expand All @@ -44,6 +45,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- [A tour of math typesetting](https://tailwind-nextjs-starter-blog.vercel.app/blog/deriving-ols-estimator)
- [Simple MDX image grid](https://tailwind-nextjs-starter-blog.vercel.app/blog/pictures-of-canada)
- [Example of long prose](https://tailwind-nextjs-starter-blog.vercel.app/blog/the-time-machine)
- [Example of Nested Route Post](https://tailwind-nextjs-starter-blog.vercel.app/blog/nested-route/introducing-multi-part-posts-with-nested-routing)

## Quick Start Guide

Expand Down
2 changes: 1 addition & 1 deletion components/Tag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from 'next/link'
import { kebabCase } from '@/lib/utils'
import kebabCase from '@/lib/utils/kebabCase'

const Tag = ({ text }) => {
return (
Expand Down
4 changes: 3 additions & 1 deletion data/blog/introducing-tailwind-nextjs-starter-blog.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: 'Introducing Tailwind Nexjs Starter Blog'
date: '2021-01-12'
lastmod: '2021-01-18'
lastmod: '2021-05-08'
tags: ['next-js', 'tailwind', 'guide']
draft: false
summary: 'Looking for a performant, out of the box template, with all the best in web technology to support your blogging needs? Checkout the Tailwind Nextjs Starter Blog template.'
Expand Down Expand Up @@ -44,6 +44,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- Automatic image optimization via [next/image](https://nextjs.org/docs/basic-features/image-optimization)
- Flexible data retrieval with [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote)
- Support for tags - each unique tag will be its own page
- Support for nested routing of blog posts
- SEO friendly with RSS feed, sitemaps and more!

## Sample posts
Expand All @@ -53,6 +54,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea
- [A tour of math typesetting](/blog/deriving-ols-estimator)
- [Simple MDX image grid](/blog/pictures-of-canada)
- [Example of long prose](/blog/the-time-machine)
- [Example of Nested Route Post](/blog/nested-route/introducing-multi-part-posts-with-nested-routing)

## Quick Start Guide

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Introducing Multi-part Posts with Nested Routing
date: '2021-05-02'
tags: ['multi-author', 'next-js', 'feature']
draft: false
summary: 'The blog template supports posts in nested sub-folders. This can be used to group posts of similar content e.g. a multi-part course. This post is itself an example of a nested route!'
---

# Nested Routes

The blog template supports posts in nested sub-folders. This helps in organisation and can be used to group posts of similar content e.g. a multi-part series. This post is itself an example of a nested route! It's located in the `/data/blog/nested-route` folder.

## How

Simplify create multiple folders inside the main `/data/blog` folder and add your `.md`/`.mdx` files to them. You can even create something like `/data/blog/nested-route/deeply-nested-route/my-post.md`

We use Next.js catch all routes to handle the routing and path creations.

## Use Cases

Here's some reasons to use nested routes

- More logical content organisation (blogs will still be displayed based on the created date)
- Multi-part posts
- Different sub-routes for each author
- Internationalization (though it would be recommended to use [Next.js built in i8n routing](https://nextjs.org/docs/advanced-features/i18n-routing))

## Note

- The previous/next post links at bottom of the template is currently sorted by date. One could explore modifying the template to refer the reader to the previous/next post in the series, rather than by date.
30 changes: 18 additions & 12 deletions lib/mdx.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import MDXComponents from '@/components/MDXComponents'
import fs from 'fs'
import matter from 'gray-matter'
import visit from 'unist-util-visit'
import renderToString from 'next-mdx-remote/render-to-string'
import path from 'path'
import readingTime from 'reading-time'
import renderToString from 'next-mdx-remote/render-to-string'

import MDXComponents from '@/components/MDXComponents'
import visit from 'unist-util-visit'
import imgToJsx from './img-to-jsx'
import getAllFilesRecursively from './utils/files'

const root = process.cwd()

Expand All @@ -24,8 +24,11 @@ const tokenClassNames = {
comment: 'text-gray-400 italic',
}

export async function getFiles(type) {
return fs.readdirSync(path.join(root, 'data', type))
export function getFiles(type) {
const prefixPaths = path.join(root, 'data', type)
const files = getAllFilesRecursively(prefixPaths)
// Only want to return blog/path and ignore root
return files.map(file => file.slice(prefixPaths.length + 1))
}

export function formatSlug(slug) {
Expand All @@ -39,8 +42,8 @@ export function dateSortDesc(a, b) {
}

export async function getFileBySlug(type, slug) {
const mdxPath = path.join(root, 'data', type, `${slug}.mdx`)
const mdPath = path.join(root, 'data', type, `${slug}.md`)
const mdxPath = path.join(root, 'data', type, `${slug.join('/')}.mdx`)
const mdPath = path.join(root, 'data', type, `${slug.join('/')}.md`)
const source = fs.existsSync(mdxPath)
? fs.readFileSync(mdxPath, 'utf8')
: fs.readFileSync(mdPath, 'utf8')
Expand Down Expand Up @@ -86,16 +89,19 @@ export async function getFileBySlug(type, slug) {
}
}

export async function getAllFilesFrontMatter(type) {
const files = fs.readdirSync(path.join(root, 'data', type))
export async function getAllFilesFrontMatter(folder) {
const prefixPaths = path.join(root, 'data', folder)

const files = getAllFilesRecursively(prefixPaths)

const allFrontMatter = []

files.forEach((file) => {
const source = fs.readFileSync(path.join(root, 'data', type, file), 'utf8')
const fileName = file.slice(prefixPaths.length + 1)
const source = fs.readFileSync(file, 'utf8')
const { data } = matter(source)
if (data.draft !== true) {
allFrontMatter.push({ ...data, slug: formatSlug(file) })
allFrontMatter.push({ ...data, slug: formatSlug(fileName) })
}
})

Expand Down
5 changes: 3 additions & 2 deletions lib/tags.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import fs from 'fs'
import matter from 'gray-matter'
import path from 'path'
import { kebabCase } from './utils'
import { getFiles } from './mdx'
import kebabCase from './utils/kebabCase'

const root = process.cwd()

export async function getAllTags(type) {
const files = fs.readdirSync(path.join(root, 'data', type))
const files = await getFiles(type)

let tagCount = {}
// Iterate through each post, putting all found tags into `tags`
Expand Down
20 changes: 20 additions & 0 deletions lib/utils/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import fs from 'fs'
import path from 'path'

const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)

const flatternArray = (input) =>
input.reduce((acc, item) => [...acc, ...(Array.isArray(item) ? item : [item])], [])

const map = (fn) => (input) => input.map(fn)

const walkDir = (fullPath) => {
return fs.statSync(fullPath).isFile() ? fullPath : getAllFilesRecursively(fullPath)
}

const pathJoinPrefix = (prefix) => (extraPath) => path.join(prefix, extraPath)

const getAllFilesRecursively = (folder) =>
pipe(fs.readdirSync, map(pipe(pathJoinPrefix(folder), walkDir)), flatternArray)(folder)

export default getAllFilesRecursively
4 changes: 3 additions & 1 deletion lib/utils.js → lib/utils/kebabCase.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const kebabCase = (str) =>
const kebabCase = (str) =>
str &&
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((x) => x.toLowerCase())
.join('-')

export default kebabCase
15 changes: 7 additions & 8 deletions pages/blog/[slug].js → pages/blog/[...slug].js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import fs from 'fs'
import hydrate from 'next-mdx-remote/hydrate'
import { getFiles, getFileBySlug, getAllFilesFrontMatter, formatSlug } from '@/lib/mdx'
import PostLayout from '@/layouts/PostLayout'
import MDXComponents from '@/components/MDXComponents'
import PageTitle from '@/components/PageTitle'
import PostLayout from '@/layouts/PostLayout'
import generateRss from '@/lib/generate-rss'
import { formatSlug, getAllFilesFrontMatter, getFileBySlug, getFiles } from '@/lib/mdx'
import fs from 'fs'
import hydrate from 'next-mdx-remote/hydrate'

export async function getStaticPaths() {
const posts = await getFiles('blog')

const posts = getFiles('blog')
return {
paths: posts.map((p) => ({
params: {
slug: formatSlug(p),
slug: formatSlug(p).split('/'),
},
})),
fallback: false,
Expand All @@ -21,7 +20,7 @@ export async function getStaticPaths() {

export async function getStaticProps({ params }) {
const allPosts = await getAllFilesFrontMatter('blog')
const postIndex = allPosts.findIndex((post) => post.slug === params.slug)
const postIndex = allPosts.findIndex((post) => formatSlug(post.slug) === params.slug.join('/'))
const prev = allPosts[postIndex + 1] || null
const next = allPosts[postIndex - 1] || null
const post = await getFileBySlug('blog', params.slug)
Expand Down
8 changes: 4 additions & 4 deletions pages/tags.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import siteMetadata from '@/data/siteMetadata'
import { kebabCase } from '@/lib/utils'
import { getAllTags } from '@/lib/tags'
import Tag from '@/components/Tag'
import Link from '@/components/Link'
import { PageSeo } from '@/components/SEO'
import Tag from '@/components/Tag'
import siteMetadata from '@/data/siteMetadata'
import { getAllTags } from '@/lib/tags'
import kebabCase from '@/lib/utils/kebabCase'

export async function getStaticProps() {
const tags = await getAllTags('blog')
Expand Down
12 changes: 6 additions & 6 deletions pages/tags/[tag].js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import fs from 'fs'
import path from 'path'
import { kebabCase } from '@/lib/utils'
import { getAllFilesFrontMatter } from '@/lib/mdx'
import { getAllTags } from '@/lib/tags'
import { PageSeo } from '@/components/SEO'
import siteMetadata from '@/data/siteMetadata'
import ListLayout from '@/layouts/ListLayout'
import { PageSeo } from '@/components/SEO'
import generateRss from '@/lib/generate-rss'
import { getAllFilesFrontMatter } from '@/lib/mdx'
import { getAllTags } from '@/lib/tags'
import kebabCase from '@/lib/utils/kebabCase'
import fs from 'fs'
import path from 'path'

const root = process.cwd()

Expand Down
84 changes: 0 additions & 84 deletions public/index.xml

This file was deleted.

0 comments on commit 735c954

Please sign in to comment.