2023 Portfolio Refresh
December 18, 2023 (11mo ago)
I enjoy experimenting with various technologies on my personal website, utilizing it as a sandbox for various Next.js projects, consistently pushing boundaries to explore and test different tools and approaches. This website was entirely constructed from the ground up, continually evolving through migrations that integrate a blend of newly discovered technologies and libraries.
Last week, I made significant updates to ensure better performance and functionality. Motivated by Lee Robinson's advice regarding the significance of documenting project updates, I decided to document the newly added functional and performance features.
With this new update I focused on two main areas:
- Content Management
- Performance.
Content Management
Initially, my website was built using Next.js and a CMS. Selecting the right CMS was tricky due to the complexities involved. Eventually, I chose GhostCMS for its simplicity and convenient JavaScript Content API, which made integration quick and easy as compared to the others.
However, challenges with GhostCMS affected data formatting. Limited control over HTML data required substantial front-end styling. This highlighted the impact of the Ghost API on data handling and the effort needed for front-end design. Also, Ghost’s membership functionality isn't currently compatible with headless setups, potentially limiting expansion into memberships.
Recently, I contemplated migrating away from GhostCMS, considering alternative CMS options, including leveraging Notion's API. After comprehensive analysis, I opted for MDX—a developer-centric solution that addresses limitations encountered with GhostCMS, providing tailored flexibility for my requirements.
MDX
MDX (Markdown eXtended) combines Markdown and JSX syntax, offering enhanced flexibility for creating customizable components. It allows direct inclusion of JSX components within content, enabling dynamic content creation that combines Markdown with JSX. Also, you can further enhance its capabilities using plugins such as rehype
and remark
for additional feature expansions.
Setting up for Next.js
While Next's package @next/mdx
is a viable option, I chose @next-remote-mdx
due to its expanded capabilities. Additionally, I utilized gray-matter
to enable metadata parsing within MDX.
// Example MDX file structure
---
title: "My MDX Example"
author: "Kelvin Yelyen"
date: "2023-12-31"
---
import { Callout } from '@components/callout.jsx'
# Hello, MDX with Gray Matter!
This is a **paragraph** in MDX. You can use _Markdown_ syntax here.
- Here's a list item.
- And another one.
<Callout>
Callout Text
</Callout>
MDX allows direct inclusion of JSX components within content, as demonstrated with <Callout></Callout>
, combining Markdown with JSX for dynamic content creation.
Processing .mdx files
Here's a snippet illustrating the handling and processing of .mdx files within a Next.js environment for blog content:
// JavaScript code handling .mdx files
import fs from "fs"
import path from "path"
import matter from "gray-matter"
import { formatTimeAgo, formatDateTimeFull } from "./dates"
// Functions for file handling and content retrieval
const Dir = "content/blog"
function generateSlugsFromFiles() {
const files = fs.readdirSync(path.join(bDir))
const paths = files.map((filename) => ({
slug: filename.replace(".mdx", ""),
}))
return paths
}
function getPost({ slug }) {
const markdownFile = fs.readFileSync(
path.join(Dir, slug + ".mdx"),
"utf-8"
)
const { data: frontMatter, content } = matter(markdownFile)
return {
metadata: {
...frontMatter,
publishedAtFormatted: formatDateTimeFull(frontMatter.publishedAt),
},
slug,
content,
}
}
// ...
export { generateSlugsFromFiles, getPost, getBlogContent }
This set of functions exemplifies the handling and processing of .mdx files within a Next.js environment for managing blog content. By utilizing these functions, we can generate slugs, retrieve specific posts, and acquire sorted blog content efficiently.
Performance
Migrating from Page Router to App Router
In this update, a significant move involved transitioning from the conventional Page Router to the advanced App Router. Both Next.js routers leverage file-based routing, differing in how routes are defined and organized. App Router uses nested folders for route definition, providing additional hierarchy and organization, while Page Router directly maps routes to files. The choice between the two depends on project complexity and the preferred level of organization.
The default choice between Server Components and Client Components depends on the routing strategy—Server Components are default in App Router, emphasizing server-side rendering, while Client Components are default in Page Router, prioritizing client-side rendering. The selection depends on specific requirements and architecture.
Data Fetching
- Page Router: Offers a variety of data fetching methods, including
getServerSideProps
,getStaticProps
,getStaticPaths
andgetInitialProps
. These methods cater to different scenarios such as server-side rendering, static site generation, and legacy universal data loading. - App Router: Emphasizes the use of the generic
fetch
function for client-side data fetching. This method is suitable for scenarios where data is loaded dynamically on the client side, it’s much simpler in my opinion.
In my experience navigating between App Router and Page Router in Next.js, several factors influenced my decision-making process. The considerations encompassed aspects such as SEO, performance, flexibility, and the effort required for migration.
I found that leveraging App Router, with its default server-side rendering for pages in the app directory, offered potential SEO advantages. The inclusion of Server Components within App Router enhanced performance, aligning well with the specific performance goals of my project. Notably, the flexibility provided by App Router, especially concerning nested layouts and data fetching, added a layer of convenience to the development process.
However, I acknowledged that migrating existing projects to App Router might involve a substantial effort, requiring careful evaluation. For new projects, I opted to start with App Router, drawn to its modern features and flexibility. On the other hand, for existing projects, a thorough assessment of potential benefits preceded any consideration of migration.
I found that Page Router sufficed for simpler projects with straightforward routing needs, while App Router emerged as the preferable choice for projects demanding complex routing and extensive data requirements. These considerations formed a strategic approach tailored to the unique characteristics of each project.
Refining Metadata, SEO, and Sitemaps
Recognizing the pivotal impact of metadata, SEO strategies, and sitemaps on enhancing website performance, I directed my efforts toward refining these critical aspects. The process involved creating robust metadata to provide comprehensive information, implementing effective SEO strategies to improve search engine visibility, and ensuring regular updates to XML sitemaps for a seamless user experience. These concerted efforts were instrumental in optimizing the website's overall performance, contributing to improved discoverability and a more user-friendly interface.