Moving to NextJS
- Published on
Still learning JS and TS and solving blog problems, I moved the blog from Gatsby to NextJS.
Now the blog is hosted on vercel, built with NextJS and TailwindCSS.
I encountered so many difficulties before in the Gatsby version.
Thanks to timlrx’s starter, I don’t need to manage data and typography from scratch like in Gatsby. (I struggled with dark mode, markup list gap, and prism.js bad style)
Modifications
I modified something else except for changing designs from the starter to my last version.
- Dark Mode: Changed background
#22272E
and text colorADBAC7
in dark mode with Github dark dimmed style. Changed cards and buttons hovering color tobg-opacity-10
- Images: Using
NextJS Images
inPostCard
and the cover of posts. It helped minified the size of the original images. However, because it requires me to offer width and depth in advance, It is a little challenging to apply it in markdown files. Maybe I would usemdx
but I still don’t want to break pure markdown files. I will talk about this later. - Minor Modifications
When I was trying to upgrade some dependencies, I found it difficult to solve some problems. For example, if I upgrade React to 18 and NextJS to 13, everything seems to be OK in dev mode. But some buttons don’t work after officially building and running it.
- React 18 “Hydration failed because the initial UI does not match what was rendered on the server.” https://stackoverflow.com/questions/71706064/react-18-hydration-failed-because-the-initial-ui-does-not-match-what-was-render
- Package subpath ’./jsx-runtime.js’ is not defined by “exports” update pkg
Migrate To TypeScript
As said in TypeScript Documents:
Most programming languages would throw an error when these sorts of errors occur, some would do so during compilation — before any code is running. When writing small programs, such quirks are annoying but manageable; when writing applications with hundreds or thousands of lines of code, these constant surprises are a serious problem.
I think the problem I encountered may be solved if I can migrate to TypeScript. At least, I won’t spend so much time searching google, stackoverflow, and GitHub to figure out why everything was OK but the buttons broke after building, which is totally a waste of energy and I can’t find why.
References:
TypeScript has so many features to learn. Now, I just moved from JavaScript in an unstrict way to make it work first.
it’s like moving Python to Java😂:
const timeMap: Map<string, Map<string, Array<FrontMatter>>> = new Map()
for (const post of posts) {
if (post.date !== null) {
const year: string = new Date(post.date).getFullYear().toString()
const month: string = new Date(post.date).toDateString().split(' ')[1]
if (!timeMap.has(year)) {
timeMap.set(year, new Map())
}
if (!timeMap.get(year).has(month)) {
timeMap.get(year).set(month, [])
}
timeMap.get(year).get(month).push(post)
}
}
There are so many files from the original starter I haven’t added types. So bugs may still exist. I plan to upgrade React and NextJS after I fully migrate to TypeScript.
After migrating to TypeScript, I tried to upgrade React and Next again but it still had bugs in the client end. I finally figured out why. It’s because the starter uses Preact in the building process. However, Preact doesn’t yet shim the new hooks in React 18. So I just removed it and the upgrade succeeded.
Using MarkdownX
Despite mdx
, we can use its features in md
files without the need to change the filename extension. But it is a bit annoying to edit JSX code in md
files without auto-completion.
With this starter and React framework, It’s easy to use JSX in a markdown file directly. For example:
source code:
<div className="grid grid-cols-2 gap-3">
<div>![](...)</div>
<div>![](...)</div>
</div>
The problem is my previous embedded Spotify and Apple Page iframe doesn’t work.
If I directly copy the code Apple offers, it’ll show:
Error: The
style
prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}}
when using JSX.
We need to observe JSX’s style. At the same time, to use this, we need to disable Content Security Policy (CSP).
Actually, we can just use third part API and JSX to design components and use them in mdx by ourselves. But too many components used in markdown files will break their migratability.
Custom Components
<TOCInline toc={props.toc} asDisclosure />
NextJS Images1
The problem is that I use remote images that are hosted on Aliyun OSS. So when rendering markdown files, I can not (I find I can later) get depth
and height
in advance, which is necessary for NextJS Images. So If I want to change img
to NextImage
, I have to get images metadata before the rendering process. I use image-size to get metadata and plaiceholder to get blur64
of the image (Referring this post)
Visit all nodes (We need to use unist-util-visit) to get img
nodes and addProps
for them. visit
lets us find the image nodes (in markdown, an image will be transferred to p
node and img
node). The first parameter is tree
; the second is the function that filters nodes; the third parameter is the function that does something to nodes.2
Note: Use async
, await
, Promise
.
import { visit } from 'unist-util-visit'
import imageSize from 'image-size'
import { ISizeCalculationResult } from 'image-size/dist/types/interface'
const remarkImgToJsx = () => {
return async function transformer(tree: Node): Promise<Node> {
const images: UnistImageNode[] = []
visit(
tree,
(node: UnistNodeType) =>
node.type === 'paragraph' && node.children.some((n) => n.type === 'image'),
(node: UnistNodeType) => {
const imageNode = node.children.find((n) => n.type === 'image') as UnistImageNode
images.push(imageNode)
// Change node types from p to div to avoid nesting error
node.type = 'div'
node.children = [imageNode]
}
)
for (const image of images) {
await addProps(image)
}
}
}
addProps
replaces the original attributes of image nodes with the NextJS Image attributes (blur placeholder is calculated by plaiceholder)
async function addProps(imageNode: UnistImageNode): Promise<void> {
let res: ISizeCalculationResult
let blur64: string
if (imageNode.url.startsWith('http') && !imageNode.url.endsWith('svg')) {
const imageRes = await fetch(imageNode.url)
const arrayBuffer = await imageRes.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
res = await imageSize(buffer)
blur64 = (await getPlaiceholder(buffer)).base64
;(imageNode.type = 'mdxJsxFlowElement'),
(imageNode.name = 'Image'),
(imageNode.attributes = [
{ type: 'mdxJsxAttribute', name: 'alt', value: imageNode.alt },
{ type: 'mdxJsxAttribute', name: 'src', value: imageNode.url },
{ type: 'mdxJsxAttribute', name: 'width', value: res.width },
{ type: 'mdxJsxAttribute', name: 'height', value: res.height },
{ type: 'mdxJsxAttribute', name: 'quality', value: 100 },
{ type: 'mdxJsxAttribute', name: 'placeholder', value: 'blur' },
{ type: 'mdxJsxAttribute', name: 'blurDataURL', value: blur64 },
])
}
}
Now, every original image has been changed from:
<p><img src="..." /></p>
to NexJS Images, which is on-demand image resizing.
Note: fetching remote images means that the server gets data first and the building time of the site will be much longer.
After doing these, I find that the performance of the site on the cloud is good, but the local dev version takes a long time to refresh, which drives me crazy.
Then I find that my image hoster can offer width
and height
directly by calling API, which will help me save refreshing and building time.
For example, if I call an image with URL ?x-oss-process=image/info
, I can get:
{
"FileSize": { "value": "4152561" },
"Format": { "value": "jpg" },
"FrameCount": { "value": "1" },
"ImageHeight": { "value": "3712" },
"ImageWidth": { "value": "5568" },
"ResolutionUnit": { "value": "2" },
"XResolution": { "value": "72/1" },
"YResolution": { "value": "72/1" }
}
The hoster can do some image processing but it can not offer blur64
like what plaiceholder
does. So I use this website offered by NextJS docs to get a fixed blur64
and apply it.
const res = await fetch(post.image + '?x-oss-process=image/info')
const json = await res.json()
const height = json.ImageHeight.value
const width = json.ImageWidth.value
post.imageMetadata = {
height: height,
width: width,
blurDataURL: `data:image/png;base64,${siteMetadata.blur64}`,
}
blur64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=',
Domain Blocked
In China, the domain vercel.app
is banned. But I found that a guy’s website totally on Vercel can work perfectly. I tested it and find that in most areas of China, the website is accessible. But mine breaks(I set A and CNAME with my domain). Without VPN, I can not even access it. I spent two hours checking why. I even sent an email to the guy to ask if there is something to be managed that I haven’t mentioned. It turned out that my domain zzhgo.com
is banned by GFW in China. It is so depressing. So I can only change my domain to atksoto.com
.
Footnotes
-
Content as structured data https://unifiedjs.com ↩