Feat/sub-route (#38)

* blog subroute support
* docs: update readme and blog

Co-authored-by: mrhut10 <ahut10@gmail.com>
This commit is contained in:
Timothy 2021-05-08 13:58:57 +08:00 committed by GitHub
parent 69a41932e7
commit 735c954e72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 99 additions and 119 deletions

2
.gitignore vendored
View File

@ -17,6 +17,8 @@ public/sitemap.xml
# production
/build
*.xml
# rss feed
/public/index.xml
# misc
.DS_Store

0
.husky/pre-commit Normal file → Executable file
View File

View File

@ -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!
@ -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

View File

@ -1,5 +1,5 @@
import Link from 'next/link'
import { kebabCase } from '@/lib/utils'
import kebabCase from '@/lib/utils/kebabCase'
const Tag = ({ text }) => {
return (

View File

@ -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.'
@ -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
@ -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

View File

@ -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.

View File

@ -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()
@ -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) {
@ -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')
@ -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) })
}
})

View File

@ -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`

20
lib/utils/files.js Normal file
View File

@ -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

View File

@ -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

View File

@ -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,
@ -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)

View File

@ -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')

View File

@ -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()

View File

@ -1,84 +0,0 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Next.js Starter Blog</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog</link>
<description>A blog created with Next.js and Tailwind.css</description>
<language>en-us</language>
<managingEditor>address@yoursite.com (Tails Azimuth)</managingEditor>
<webMaster>address@yoursite.com (Tails Azimuth)</webMaster>
<lastBuildDate>Tue, 12 Jan 2021 00:00:00 GMT</lastBuildDate>
<atom:link href="https://tailwind-nextjs-starter-blog.vercel.app/index.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/introducing-tailwind-nextjs-starter-blog</guid>
<title>Introducing Tailwind Nexjs Starter Blog</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/introducing-tailwind-nextjs-starter-blog</link>
<description>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.</description>
<pubDate>Tue, 12 Jan 2021 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>next-js</category><category>tailwind</category><category>guide</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/guide-to-using-images-in-nextjs</guid>
<title>Images in Next.js</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/guide-to-using-images-in-nextjs</link>
<description>In this article we introduce adding images in the tailwind starter blog and the benefits and limitations of the next/image component.</description>
<pubDate>Wed, 11 Nov 2020 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>next js</category><category>guide</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/deriving-ols-estimator</guid>
<title>Deriving the OLS Estimator</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/deriving-ols-estimator</link>
<description>How to derive the OLS Estimator with matrix notation and a tour of math typesetting using markdown with the help of KaTeX.</description>
<pubDate>Sat, 16 Nov 2019 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>next js</category><category>math</category><category>ols</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/github-markdown-guide</guid>
<title>Markdown Guide</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/github-markdown-guide</link>
<description>Markdown cheatsheet for all your blogging needs - headers, lists, images, tables and more! An illustrated guide based on Github Flavored Markdown.</description>
<pubDate>Fri, 11 Oct 2019 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>github</category><category>guide</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/the-time-machine</guid>
<title>The Time Machine</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/the-time-machine</link>
<description>The Time Traveller (for so it will be convenient to speak of him) was expounding a recondite matter to us. His pale grey eyes shone and twinkled, and his usually pale face was flushed and animated...</description>
<pubDate>Wed, 15 Aug 2018 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>writings</category><category>book</category><category>reflection</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/pictures-of-canada</guid>
<title>O Canada</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/pictures-of-canada</link>
<description>The scenic lands of Canada featuring maple leaves, snow-capped mountains, turquoise lakes and Toronto. Take in the sights in this photo gallery exhibition and see how easy it is to replicate with some MDX magic and tailwind classes.</description>
<pubDate>Sat, 15 Jul 2017 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>holiday</category><category>canada</category><category>images</category>
</item>
<item>
<guid>https://tailwind-nextjs-starter-blog.vercel.app/blog/code-sample</guid>
<title>Sample .md file</title>
<link>https://tailwind-nextjs-starter-blog.vercel.app/blog/code-sample</link>
<description>Example of a markdown file with code blocks and syntax highlighting</description>
<pubDate>Tue, 08 Mar 2016 00:00:00 GMT</pubDate>
<author>address@yoursite.com (Tails Azimuth)</author>
<category>markdown</category><category>code</category><category>features</category>
</item>
</channel>
</rss>