Introduction#

GitHub this note shows how to build a peronsal blog which tracking my PIRF challenge. The challenge means each day I will practice English speaking and record audios. demo link

Setup Project#

Let create a new next.js project

npx create-next-app@latest

Project structure

|--app
|--blog
|--[slug]
|--page.tsx
|--page.tsx
|--layout.css
|--global.css
|--components
|--footer.tsx
|--header.tsx
|--hero.tsx
|--book
|--page.tsx
|--tailwind.config.js
|--next.config.js
|--package.json

Toggle Theme#

Update the tailwind.config.js to enable dark theme

module.exports = {
darkMode: 'class',
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}'
],
theme: {
extend: {
backgroundImage: {},
fontFamily: {
mplus: ["'M PLUS Rounded 1c'", 'Verdana', 'sans-serif']
}
}
},
plugins: []
}

Add the mplus theme to global.css also

@import url('https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@300;500;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;

Create a button to toggle theme

'use client'
import { useEffect, useState } from 'react'
const Header = () => {
const [theme, setTheme] = useState('dark')
useEffect(() => {
localStorage.setItem('theme', theme)
if (theme === 'light') {
document.documentElement.classList.remove('dark')
} else {
document.documentElement.classList.add('dark')
}
}, [theme])
return (
<div className="bg-slate-200 dark:bg-slate-900 font-mplus font-semibold shadow-sm py-2">
<div className="max-w-4xl mx-auto px-3 flex flex-row justify-between items-center">
<a href="/">
<button className="dark:text-white">Hai Tran</button>
</a>
<button
className="bg-orange-400 px-10 rounded-sm py-3"
onClick={() => {
if (theme === 'dark') {
setTheme('light')
} else {
setTheme('dark')
}
}}
>
Toggle Theme
</button>
</div>
</div>
)
}
export default Header

Hero Section#

Let create a nice hero section with image background or loop video (TODO)

const Hero = () => {
return (
<div className="relative h-80 dark:bg-slate-800 flex items-center justify-center">
<img
src="/singapore.jpg"
className="absolute w-full h-full object-cover opacity-30"
></img>
<h1 className="dark:text-white font-mplus font-semibold text-3xl z-10">
Web Development on AWS
</h1>
</div>
)
}
export default Hero

and simple footer

const Footer = () => {
return (
<footer className="dark:text-white dark:bg-slate-900 bg-gray-200 text-gray-00 py-4">
<div className="mx-auto max-w-5xl text-center text-base">
Copyright &copy; 2023 entest, Inc
</div>
</footer>
)
}
export default Footer

Tailwind Grid#

Let create the home page with a grid of all blog posts in two columns

export default async function Home() {
const posts = await listPosts()
return (
<main className="min-h-screen">
<Hero></Hero>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 dark:bg-slate-800 max-w-4xl mx-auto px-3 dark:text-white pt-5 pb-10">
{posts.map((post, id) => {
return (
<div key={id}>
<a href={post.link} className="cursor-pointer">
<img src={post.poster}></img>
</a>
<p className="mt-2">{post.description}</p>
</div>
)
})}
</div>
</main>
)
}