2023 Blog Site Update Notes
Web Development
January 19, 2023
It’s that time again! Time to do some spring cleaning on the blog site and check out the latest and greatest in front-end tech. There’s been some great progress in static site frameworks, so let’s chat about my latest journey.
Updated Blog
Framework: Next.js 13
CSS: TailwindCSS
CMS: Notion
Deployment: Vercel
Repo: Updated Site Repo
As this is a technical blog, let’s go into a little more detail about the implementation.
Next JS
I’ve been following the progress of Next for a while now and I can say, after implementing it myself, that it’s my new favorite static site framework. I am using version 13, but I’m not using some of the newer features such as the new /app
directory or Turbopack (the rust-based successor to Webpack) until they come out of beta.
Notion as a CMS
I’m a huge Notion Junkie.
I organize all my daily tasks, projects, notes, and keep several databases for movies, TV, and readings all in Notion. They recently opened their API to the public which is how it can be used as a CMS. It’s not as fully featured as some CMS’s, but it still works great. There are a few ways to get this going, so I’ll go over a few.
Setting up Notion Database
In Notion I just setup a database with the following schema:
- Name (Where the content is)
- Slug
- Description
- Tags (Multiselect)
- Published (Boolean)
- Date (date)
- (Optional) Wordcount (Number)
Option 1: Rendering with React Notion X
This library fetches data on a “Page” level and renders an exact clone of what it looks like in Notion. There’s not a whole lot of flexibility for styling, but it’s a quick and easy way to add content to an external site.
1import { NotionAPI } from 'notion-client' 2 3/* 4Chrome Development Tools > Application > Cookie > and Copy the 5token_v2 and notion_user_id 6*/ 7const notion = new NotionAPI({ 8 activeUser: process.env.NOTION_ACTIVE_USER, 9 authToken: process.env.NOTION_TOKEN_V2 10}) 11 12// Page ID grabbed from the url 13const recordMap = await notion.getPage('067dd719a912471ea9a3ac10710e7fdf') 14 15... 16 17import * as React from 'react' 18import { NotionRenderer } from 'react-notion-x' 19 20export default ({ recordMap }) => ( 21 <NotionRenderer recordMap={recordMap} fullPage={true} darkMode={false} /> 22)
Option 2: Rendering with Notion as Markdown (What I used)
- https://github.com/makenotion/notion-sdk-js
- https://github.com/souvikinator/notion-to-md
- React Markdown
For this approach, you’ll need to use the Notion official API client to download the page, convert it to markdown using the Notion to Markdown library, then use React Markdown to render the data.
1// Start the notion client 2const notion = new Client({ 3 auth: process.env.NOTION_TOKEN, 4}); 5 6// Fetch all published posts from the database id 7export const getAllPublished = async (): Promise<PostMetadata[]> => { 8 const posts = await notion.databases.query({ 9 database_id: process.env.DATABASE_ID || '', 10 filter: { 11 property: "Published", // Only grab entries that are published 12 checkbox: { 13 equals: true, 14 }, 15 }, 16 sorts: [ 17 { 18 property: "Date", 19 direction: "descending", 20 }, 21 ], 22 }); 23 24 return posts.results; 25}; 26 27// Fetch a single post and convert it to markdown 28const n2m = new NotionToMarkdown({ notionClient: notion }); 29export const getSingleBlogPostBySlug = async (slug: string): Promise<SinglePost> => { 30 const response = await notion.databases.query({ 31 database_id: process.env.DATABASE_ID || '', 32 filter: { 33 property: "Slug", 34 formula: { 35 string: { 36 equals: slug, 37 }, 38 }, 39 }, 40 }); 41 const page: any = response.results[0]; 42 const metadata = getPageMetaData(page); 43 const mdblocks = await n2m.pageToMarkdown(page.id); 44 const mdString = n2m.toMarkdownString(mdblocks); 45 metadata.cover = page.cover.external.url; 46 return { 47 metadata, 48 markdown: mdString, 49 }; 50}; 51 52... 53 54// Not every markdown tag is defined, so you’ll need to override using the 55// React Markdown settings. 56<ReactMarkdown 57 components={{ 58 // When the code tag is used, replace with custom code component with highlighting 59 code({ node, inline, className, children, ...props }) { 60 const match = /language-(\w+)/.exec(className || '') 61 return !inline && match ? ( 62 <CodeBlock 63 codestring={String(children).replace(/\n$/, '')} 64 language={match[1]} 65 /> 66 ) : ( 67 <code className={className} {...props}> 68 {children} 69 </code> 70 ) 71 } 72 }}> 73 {markdown} 74</ReactMarkdown>
There are plenty more options, but these are what I determined to be the “best” in my own opinion.
TailwindCSS
I’ve been creating and organizing my own CSS/SCSS files for years now, so I thought it was time to try something out-of-the-box. I know there are plenty of frameworks and design systems to choose from, but I decided to see what all the buzz was about with Tailwind.
The big feature that caught my attention was it’s use of utility classes instead of semantic class names. Basically, for (almost) every CSS property, they’ve created a class that implements it. For instance if you want an element to have rounded edges you just add the class rounded
or the other preset radius classes and now the element will be rounded. You can compose multiple classes to style specific elements. For example:
Vanilla
1.header { 2 padding: 60px; 3 text-align: center; 4 background: #000; 5 color: white; 6 font-size: 30px; 7} 8 9<h1 class="header">Heading 1</h1>
Tailwind
1<h1 class="p-10 text-center bg-black text-white text-3xl">Heading 1</h1>
It can definitely add some clutter to the HTML but it decreases complexity by having only one file. All the settings and definitions can be customized so you aren’t tied into their designs. Implementing styling this way can also cause repetition in your code. A good thing though, is that it forces the code to be split up into reusable components, which is good design anyways.
All in all, even though it takes a little while to get used to, I think it’s worth it in the end.
Future Features
I still haven’t implemented all of the features that I wanted, but here’s my wish list:
- Book Widget - Just a little widget that grabs the book I’m currently reading from my Notion book DB
- Custom support for more Notion blocks - React markdown doesn’t pick up all the notion markdown tags, so I’ll need to add more functionality to support more advanced features
- Update my Avatar - I made this a few years ago and it’s likeness is not really up to date…
- SEO - I just put in the basic headers, but I’ll look into adding more
- Open Graph - This is mostly for sharing with social media (mostly twitter) but I do think it’s important these days
- Search/Filter by Tag - Since all the blog entries have assigned tags, it would be nice to be able to filter through them.
- Discussion and Social Interactions - I would like to have some “like buttons” or other ways for users to interact.
- Better Design - This site is pretty bare-bones. Normally I like to make things a little more colorful.
Latest from the Blog
Currently Reading
![Chad Avatar](/_next/image?url=https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F8732d4dd-8d29-482c-8c1a-be6bca59b748%2Fab6ec887-7179-4ba3-8947-be3bc6a4ff54%2FIMG_3816.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45HZZMZUHI%252F20240908%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20240908T011125Z%26X-Amz-Expires%3D3600%26X-Amz-Signature%3D692236e9cde6dd77378a2be52e180a18d85397494d248197e588f9903197651f%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject&w=828&q=75)