upstream #1
							
								
								
									
										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> | ||||
		Reference in New Issue
	
	Block a user