2021 - Next.js Build Notes
These notes start from the point when I moved off mdx-bundler and onto next-mdx-remote. I started from scratch.
Initial setup
mkdir alanwsmith.com
cd alanwsmith.com
npx create-next-app .
npm i next-mdx-remote gray-matter
npm i @netlify/plugin-nextjs
That got everything ready to go based off the work done on https://next-mdx-remote-blog-example.netlify.app/
Moved all components into their own files since next-mdx-remote doesn't work with them inside of files.
The data has to be added directly in the component as well, like this:
<Checklist data={`
Start Monster Cat Audio
Set Monster Cat Audio to Shuffle
Mute headphones
Switch OBS to main scene and check that music and vocals are working
`} />
Installed Tailwind from these instructions: via: https://tailwindcss.com/docs/guides/nextjs
Starting with
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Then ran this
npx tailwindcss init -p
which created `tailwind.config.js` and `postcss.config.js`
The next step was to put this in `tailwind.config.js`
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
Then replaced the contents of ./styles/global.css with:
-- css
@tailwind base;
@tailwind components;
@tailwind utilities;
That gets the basic framework in place.
Setup to use a basic LayoutMain file that has tailwind in it:
file: ./components/LayoutMain.js
export default function LayoutMain(props) {
return (
<>
<div className="bg-gray-800 min-h-screen">
<main className="pb-16 mx-auto container pt-4 px-6 max-w-screen-md">
{props.content}
</main>
</div>
</>
)
}
Then updated the index.js and [slug].js file to use it with:
file: ./pages/index.js
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import Link from 'next/link'
import LayoutMain from '../components/LayoutMain'
export default function HomePage({ posts }) {
return (
<>
<LayoutMain
content={
<>
<h1>This is the home page</h1>
<p>Stuff broke... I fix</p>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/posts/${post.slug}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
</>
}
/>
</>
)
}
export async function getStaticProps({ params }) {
const contentDir = path.join(process.cwd(), '_posts')
const posts = fs
.readdirSync(contentDir)
.filter((filePath) => /\.mdx?$/.test(filePath))
.map((filePath) => {
const slug = filePath.replace(/\.mdx$/, '')
const { content, data } = matter(
fs.readFileSync(path.join(contentDir, filePath))
)
return {
title: data.title,
slug: slug,
}
})
return {
props: {
posts: posts,
},
}
}
file: ./pages/posts/[slug].js
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Link from 'next/link'
const CONTENT_DIR = path.join(process.cwd(), '_posts')
import LayoutMain from '../../components/LayoutMain'
import Checklist from '../../components/Checklist'
import ReadOnlyChecklist from '../../components/ReadOnlyChecklist'
import YouTubeVideo from '../../components/YouTubeVideo'
import VimeoVideo from '../../components/VimeoVideo'
export default function Post({ source, frontmatter }) {
return (
<>
<div>
<Link href="/">
<a>Home Page</a>
</Link>
</div>
<LayoutMain
content={
<>
<h1>{frontmatter.title}</h1>
<MDXRemote
{...source}
components={{
Checklist,
ReadOnlyChecklist,
YouTubeVideo,
VimeoVideo,
}}
/>
</>
}
/>
</>
)
}
export async function getStaticProps({ params }) {
const { content, data } = matter(
fs.readFileSync(path.join(CONTENT_DIR, `${params.slug}.mdx`))
)
const mdxSource = await serialize(content)
return { props: { source: mdxSource, frontmatter: data } }
}
export async function getStaticPaths() {
const paths = fs
.readdirSync(CONTENT_DIR)
.filter((path) => /\.mdx?$/.test(path))
.map((fileName) => fileName.replace(/\.mdx$/, ''))
.map((slug) => ({ params: { slug: slug } }))
return {
paths: paths,
fallback: false,
}
}
Installed prismjs with:
npm i prismjs
Added `import 'prismjs/themes/prism-tomorrow.css'` to _app.js so it looks like this:
import '../styles/globals.css'
import 'prismjs/themes/prism-tomorrow.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
And then updated ./components/LayoutMain.js so it looks like this:
const prism = require('prismjs')
import { useEffect } from 'react'
export default function LayoutMain(props) {
useEffect(() => {
prism.highlightAll()
}, [])
return (
<>
<div className="bg-gray-800 min-h-screen">
<main className="pb-16 mx-auto container pt-4 px-6 max-w-screen-md">
{props.content}
</main>
</div>
</>
)
}
That gets the syntax highlighting in place so that code fences like:
const someThing = 'An example'
Render as:
const someThing = 'An example'
Added in favicons and header stuff and nav. see the github for details.
Using next/image requires importing the images directly to get the benefits of local functionality. Unfortunately next-mdx-remote doesn't handle that. So, I ended up created a `components/Img.js` file with:
import Image from 'next/image'
import black_widow_20051026_230734a1 from '../_images/black_widow_20051026_230734a1.jpg'
const imgMap = {
black_widow_20051026_230734a1: black_widow_20051026_230734a1,
}
export default function Img({ src, alt = 'image alt text unavailable' }) {
return <Image src={imgMap[src]} alt={alt} />
}
That gets included in the `[slug].js` file with the other components (via: `import Img from '../../components/Img'`) and passed to `MDXRemote` like this:
<MDXRemote
{...source}
components={{
Checklist,
Img,
ReadOnlyChecklist,
YouTubeVideo,
VimeoVideo,
}}
/>
Then, for every image that's going to be called I create an import like:
import black_widow_20051026_230734a1 from '../_images/black_widow_20051026_230734a1.jpg'
and a reference in a mapping object so I can access it like this:
const imgMap = {
black_widow_20051026_230734a1: black_widow_20051026_230734a1,
// other images here
}
I make sure the image filenames work as variable names so I can keep the same name in place across the board.
Then, to use the image, I call it like this in the MDX source files:
-- html
<Img
src="black_widow_20051026_230734a1"
alt="A dead black widow spider showing the red hour-glass shape on the belly"
/>
I build the `Img.js` file with a script before I publish that grabs all the images in the `_images` directory and makes an entry for each one. That way, all I need to do is drop in an image with a valid name and then use it in the `<Img>` tag. The rest of the work is automated.
I'd prefer to keep the images in sub-directories by year but that's more than I want to tackle at the moment. Getting the images in place in generally was the key. I can optimize later if that becomes necessary.
Something that's cool about this is that it will let me customize image calls further if I want since there's a layer of abstraction between the content and the `<Image>` call. For example, instead of just sending a style tag I could send an attribute that adds extra divs and puts in captions, etc... In fact, now that I think about it, that's exactly what I'm going to do.
It's a bit of a hack, but it works.
TODO: Figure out if this impacts the performance of the site as the number of images goes up since they are all being imported.