Published on

Blog 从 Hugo 迁移到 Tailwind Next.js Blog

  • avatar

Table of Contents

migration repo

git checkout -b hugo
git push origin hugo

git branch -D main
git checkout --orphan main

rm -rf ./*
rm -rf .forestry .gitignore .gitmodules

 cp -rav ~/repo/blog/* .

 cp -rav ~/repo/blog/* .

 meld . ~/repo/blog/
 bcompare . ~/repo/blog/

 git remote add origin [email protected]:ttys3/

 git push origin main -f

Guide to MDX

build Failed

plugin "@netlify/plugin-nextjs" failed

Error: The directory "/opt/build/repo/public" does not contain a Next.js production build. Perhaps the build command was not run, or you specified the wrong publish directory. However, a '.next' directory was found with a production build. Consider changing your 'publish' directory to '.next' If you are using "next export" then you should set the environment variable NETLIFY_NEXT_PLUGIN_SKIP to "true".

Build configuration

Runtime Next.js

Build command npm run build


change Publish directory public => Publish directory .next

Next.js integrations

Node.js environment

Next.js on Netlify

Troubleshooting Next.js on Netlify


1. .md to .mdx rename

rnr -r -f '.md' '.mdx' .
rnr -r -f '.mdxx' '.mdx' .
❯ rg '<http(s)?://.+?>' -l | wc -l

neither Rust or Golang Regex engine support look around (look ahead or look after)

we need use a tool support PCRE like

npm i -g replace-in-file

debug the Regex :

config.js content:

module.exports = {
  files: './**/*.mdx',
  from: /(?<!\])<http(s)?:\/\/([^[\]]+?)>(?!\])/g,
  to: ' http\$1://\$2 ',
replace-in-file --configFile=config.js

remove not used frontmatter fields:

# check
rg '^slug:\s+[a-zA-Z_-]+' 

# delete
sed -E -i '/^slug:\s+[a-zA-Z_-]+/d' `rg -l slug`

3. null-safe join of tags

commit baab006f840d07fe5a1ea27a6c586a118d21870c
Author: ttyS3 <[email protected]>
Date:   2023-07-10 20:31:17 +0800

    chore: null-safe join of tags

diff --git a/layouts/ListLayout.tsx b/layouts/ListLayout.tsx
index fb967f7..7c94a9d 100644
--- a/layouts/ListLayout.tsx
+++ b/layouts/ListLayout.tsx
@@ -66,7 +66,7 @@ export default function ListLayout({
 }: ListLayoutProps) {
   const [searchValue, setSearchValue] = useState('')
   const filteredBlogPosts = posts.filter((post) => {
-    const searchContent = post.title + post.summary + post.tags.join(' ')
+    const searchContent = post.title + post.summary + (post.tags ?? []).join(' ')
     return searchContent.toLowerCase().includes(searchValue.toLowerCase())

@@ -129,7 +129,7 @@ export default function ListLayout({
                       <div className="flex flex-wrap">
-                        { => (
+                        {(tags ?? []).map((tag) => (
                           <Tag key={tag} text={tag} />
diff --git a/layouts/PostLayout.tsx b/layouts/PostLayout.tsx
index 0562fd9..c5b165c 100644
--- a/layouts/PostLayout.tsx
+++ b/layouts/PostLayout.tsx
@@ -123,7 +123,7 @@ export default function PostLayout({ content, authorDetails, next, prev, childre
                     <div className="flex flex-wrap">
-                      { => (
+                      {(tags ?? []).map((tag) => (
                         <Tag key={tag} text={tag} />
diff --git a/pages/index.tsx b/pages/index.tsx
index f8fafeb..c084f88 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -57,7 +57,7 @@ export default function Home({ posts }: InferGetStaticPropsType<typeof getStatic
                           <div className="flex flex-wrap">
-                            { => (
+                            {(tags ?? []).map((tag) => (
                               <Tag key={tag} text={tag} />
diff --git a/pages/tags/[tag].tsx b/pages/tags/[tag].tsx
index f939fc6..4437f40 100644
--- a/pages/tags/[tag].tsx
+++ b/pages/tags/[tag].tsx
@@ -23,7 +23,7 @@ export const getStaticProps = async (context) => {
   const tag = context.params.tag as string
   const filteredPosts = allCoreContent(
-      (post) => post.draft !== true && => kebabCase(t)).includes(tag)
+      (post) => post.draft !== true && (post.tags ?? []).map((t) => kebabCase(t)).includes(tag)

3. build problem

npm run build

Failed to compile.

9:36  Error: Replace `t-6·pb-8` with `b-8·pt-6`  prettier/prettier


npm run lint

> [email protected] lint
> next lint --fix --dir pages --dir components --dir lib --dir layouts --dir scripts

the fixup like:

diff --git a/pages/404.tsx b/pages/404.tsx
index 2bdf448..873137d 100644
--- a/pages/404.tsx
+++ b/pages/404.tsx
@@ -6,7 +6,7 @@ export default function FourZeroFour() {
       <PageSEO title="Page Not Found" description="Sorry we couldn't find this page :(" />
       <div className="flex flex-col items-start justify-start md:mt-24 md:flex-row md:items-center md:justify-center md:space-x-6">
-        <div className="space-x-2 pt-6 pb-8 md:space-y-5">
+        <div className="space-x-2 pb-8 pt-6 md:space-y-5">
           <h1 className="text-6xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 md:border-r-2 md:px-6 md:text-8xl md:leading-14">

empty tags still cause problem:

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
  (Static)  automatically rendered as static HTML (uses no initial props)
  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)

(node:825002) ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
RSS feed generated...
Sitemap generated...
        (post) => => GithubSlugger.slug(t)).includes(tag)

TypeError: Cannot read properties of undefined (reading 'map')
    at file:///home/user001/repo/blog/
    at Array.filter (<anonymous>)
    at generateRSS (file:///home/user001/repo/blog/


edit contentlayer.config.ts set a default value for tags:

diff --git a/contentlayer.config.ts b/contentlayer.config.ts
index 3f6c2b8..929806e 100644
--- a/contentlayer.config.ts
+++ b/contentlayer.config.ts
@@ -44,7 +44,7 @@ export const Blog = defineDocumentType(() => ({
   fields: {
     title: { type: 'string', required: true },
     date: { type: 'date', required: true },
-    tags: { type: 'list', of: { type: 'string' } },
+    tags: { type: 'list', of: { type: 'string' }, default: [] },
     lastmod: { type: 'date' },
     draft: { type: 'boolean' },
     summary: { type: 'string' },

ref contentlayer list

page bundle image support

currently does not support page bundle images, see

npm install git+

ref to


mainly hack:

dataDir is default to data

      // path is something like `/path-to-project-root/_mdx_bundler_entry_point-3e814d53-72fb-474b-8853-f64f1521e36a.mdx`
      // after contentlayer (which uses mdx-bundler)
      // `` begin with `blog/xxx`, does not include the `data` dir
      const fullpath = resolve(
        file?.data?.rawDocumentData?.sourceFilePath ? dirname( : '',

and edit contentlayer.config.ts, add to top:

import remarkCopyLinkedFiles from 'remark-copy-linked-files'

under remarkPlugins, add below before remarkGfm:

[remarkCopyLinkedFiles, {destinationDir: process.cwd() +"/public/static/images/blog/", staticPath: "/static/images/blog"}],

other ref:


Netlify supports pnpm for Node.js 16.9.0 and later.

If your site’s base directory includes a pnpm-lock.yaml file, we will run pnpm install to install the dependencies listed in your package.json.

To specify a pnpm version, you can edit your package.json file:

"packageManager": "[email protected]"

This tells Corepack to use and download your preferred pnpm version instead of the default version that Netlify sets.

In certain scenarios, you must pass additional flags to the pnpm install command. For example, some frameworks such as Nuxt 3 and Next.js require that you modify the pnpm install command. To avoid import issues with pnpm and these frameworks, use the PNPM_FLAGS environment variable and set it to --shamefully-hoist. Learn more in our Nuxt docs and Next.js docs.

pnpm support If you’re planning to use pnpm with Next.js to manage dependencies, you must do one of the following:

Set a PNPM_FLAGS environment variable with a value of --shamefully-hoist. This appends a --shamefully-hoist argument to the pnpm install command that Netlify runs.

Enable public hoisting by adding an .npmrc file in the root of your project with this content: