Feat/sub-route (#38)
* blog subroute support * docs: update readme and blog Co-authored-by: mrhut10 <ahut10@gmail.com>
This commit is contained in:
parent
69a41932e7
commit
735c954e72
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
0
.husky/pre-commit
Normal file → Executable 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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { kebabCase } from '@/lib/utils'
|
||||
import kebabCase from '@/lib/utils/kebabCase'
|
||||
|
||||
const Tag = ({ text }) => {
|
||||
return (
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
lib/mdx.js
30
lib/mdx.js
@ -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) })
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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
20
lib/utils/files.js
Normal 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
|
@ -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
|
@ -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)
|
@ -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')
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user