Building a Professional Personal Blog from Scratch with Next.js, Tailwind CSS, and Vercel: Don’t Let Bugs Keep You Up Until 2 AM!

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Quick start: Get it done in 5 minutes, no need to read much!

It’s 2 AM, the server is groaning, and that bug keeps reappearing. Who has the mental energy to analyze theoretical concepts anymore? We need a quick, compact, and effective solution to get the blog running smoothly immediately. This is the express roadmap to get your blog online in no time.

Step 1: Initialize a Next.js project

Open your terminal and type the following command. It will initialize a brand new Next.js project, pre-integrated with TypeScript and ESLint. For a personal blog, Pages Router is sufficient, so just choose No when asked about App Router.


npx create-next-app@latest ten-blog-ngu-quyen --ts --eslint

Wait a few seconds. When you see the message Success! Created ten-blog-ngu-quyen at ..., you’re done. Now, cd into the newly created project directory:


cd ten-blog-ngu-quyen

Step 2: Configure Tailwind CSS

Tailwind is truly a lifesaver when you want to build UIs quickly without wrestling with traditional CSS. Install it in your project using the following commands:


npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Execute these two commands to install Tailwind and create two important configuration files: tailwind.config.js and postcss.config.js. Next, open tailwind.config.js, then add the paths to the files using Tailwind in the content section:


// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

After that, you need to add Tailwind’s directives to your project’s main CSS file. Typically, this is ./styles/globals.css or ./app/globals.css, depending on the structure you chose. With Pages Router, the file you need to modify is ./styles/globals.css:


/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Add your custom CSS here if needed */

Step 3: Run and deploy to Vercel

Run the application to check if everything is okay:


npm run dev

Open your browser and navigate to http://localhost:3000. If you see the default Next.js page, it means everything is on the right track. Now, for deployment! This is where Vercel truly shines – everything is surprisingly simple.

  1. Ensure your project has been committed to GitHub/GitLab/Bitbucket.
  2. Access Vercel Dashboard and log in (or sign up if you don’t have an account).
  3. Select New Project, then import your repository. Vercel will automatically recognize it as a Next.js project and configure everything.
  4. Click Deploy. In a few minutes, your blog will have a public URL, for free!

See? Deployment is faster than sipping your coffee to stay awake while hunting for bugs! Now you can breathe a temporary sigh of relief because your blog is operational. However, this is just the beginning.

Detailed Explanation: Why Choose This Trio?

Dawn has broken, and the bugs have been patched. Now it’s time to look back and analyze in more detail: Why is this technology trio the ideal choice for a personal blog, especially when you want to avoid sleepless nights because of them?

Next.js: More Than Just Pure React

Next.js, while not a new framework, brings immense value to website development, especially for content-rich sites like personal blogs. Specifically:

  • Server-Side Rendering (SSR) & Static Site Generation (SSG): These are the two features that made me ‘fall in love’ with Next.js the most. Your blog’s HTML content will be pre-rendered directly on the server or during the build process, instead of waiting for the user’s browser to load and process JavaScript. This significantly improves page load speed (performance) and optimizes SEO. For a real-world example, an SSG-powered blog can load pages in 100-200ms, considerably faster than the 500-1000ms of typical SPA applications. Googlebot always prioritizes fast pages with readily available content for easier indexing.

    
            // SSG example with getStaticProps
            // pages/posts/[slug].tsx
            export async function getStaticProps(context) {
              const { slug } = context.params;
              // Fetch post data from Markdown file or API
              const post = await getPostBySlug(slug);
              return {
                props: { post },
                revalidate: 60 // Re-generate page every 60 seconds
              }
            }
    
            export async function getStaticPaths() {
              // Get list of post slugs
              const posts = await getAllPosts();
              const paths = posts.map(post => ({ params: { slug: post.slug } }));
              return {
                paths,
                fallback: false // false if all paths are generated at build time
              }
            }
            
  • Automatic Router Creation: You just need to create files in the pages (or app if using App Router) directory, and Next.js will automatically create the corresponding routes for you. This feature is extremely convenient for a blog with many articles.
  • API Routes: Suppose your personal blog needs some simple backend functionality, such as sending a contact form or tracking page views. Next.js allows you to build API endpoints directly within your project. This eliminates the need to set up a separate server, saving you significant time and resources.

Tailwind CSS: Utility is King

Initially, I also had some reservations about Tailwind CSS’s utility-first philosophy. Seeing long, sprawling classes in HTML can feel a bit ‘cluttered’. However, once you get used to it, it’s very hard to go back to traditional methods like BEM or CSS Modules. So, what’s the reason?

  • Accelerated Development: You almost never have to leave your HTML/JSX file to write CSS. All properties are provided as utility classes. By simply combining classes like flex, items-center, justify-between, p-4, text-lg, or bg-blue-500, the user interface (UI) can be completed very quickly. This boosts productivity by 30-50% compared to writing CSS manually.
  • Avoid CSS Conflicts: Each class in Tailwind represents a unique CSS property. As a result, you no longer have to worry about changing a style in one place inadvertently affecting other components. This significantly reduces minor UI bugs, especially useful in large-scale projects. A large project with over 100,000 lines of CSS can reduce UI bugs by up to 20% thanks to Tailwind.
  • Compact Final File Size: With the integrated PurgeCSS tool, Tailwind only bundles the CSS classes you actually use into the final product. This makes your blog significantly lighter, while also improving page load speed. For example, an average project can reduce CSS file size from several hundred KB to under 10KB after optimization with PurgeCSS.

Vercel: Simple Deployment, High Performance

Vercel is the company behind Next.js, so it’s no surprise that this platform is perfectly optimized for deploying Next.js applications. For me, Vercel is like a powerful and reliable ‘assistant’:

  • Automatic Deployment (Git Integration): Just push your source code to a Git repository (GitHub, GitLab, Bitbucket), and Vercel will automatically build and deploy the new version of your blog. This speed is so fast that sometimes you might push your code, turn around to pour a glass of water, and find your blog already successfully deployed. This is truly amazing for any developer who has struggled with manual CI/CD processes. Next.js projects are often deployed in just 60-90 seconds on Vercel.
  • Global CDN: Vercel leverages a powerful Content Delivery Network (CDN). As a result, your blog will be accessed at optimal speeds from anywhere in the world, because content is distributed from the server closest to the user. For example, a user in Europe will receive content from the nearest European server, instead of having to download from the US, reducing latency to under 100ms.
  • Free for Personal Projects: Vercel’s Hobby (free) plan provides sufficient resources for most personal blogs, including 100GB bandwidth and 100 builds/day. You don’t need to worry about hosting costs when you’re just starting to build your blog.

Advanced: Optimizing and Managing Content

Once the blog is stable, it’s natural to start looking for ways to improve and optimize content management. This is when slightly more ‘advanced’ techniques are needed. These are also lessons I truly learned the value of when refactoring a codebase with 50,000 lines of code – building a proper foundation from the start is incredibly important.

Content Management with Markdown (MDX)

Writing a blog solely with HTML is a nightmare! Markdown is the perfect choice. To integrate Markdown (or MDX – Markdown combined with JSX components), I often use libraries like next-mdx-remote or the remark/rehype duo. This method allows you to write content in separate .md or .mdx files, which Next.js then automatically parses and converts into HTML for display.


npm install gray-matter next-mdx-remote

Create a posts directory, for example, to contain .mdx files:


---
title: My first post
date: '2023-10-26'
excerpt: This is a sample post about building a blog.
---

# Hello world from MDX!

This is the content of your post. You can write **bold**, *italic* and use React components:

<Button variant="primary">Read more</Button>

Then in the getStaticProps of the post page, you will read and parse this file:


// pages/posts/[slug].tsx
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const POSTS_PATH = path.join(process.cwd(), 'posts');

export default function PostPage({ source, frontMatter }) {
  return (
    <div className="container mx-auto p-4">
      <h1 className="text-4xl font-bold mb-4">{frontMatter.title}</h1>
      <p className="text-gray-500 mb-6">{frontMatter.date}</p>
      <div className="prose lg:prose-xl">
        <MDXRemote {...source} />
      </div>
    </div>
  );
}

export async function getStaticProps({ params }) {
  const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`);
  const source = fs.readFileSync(postFilePath);

  const { content, data } = matter(source);

  const mdxSource = await serialize(content, { scope: data });

  return {
    props: {
      source: mdxSource,
      frontMatter: data,
    },
  };
}

// getStaticPaths is also needed to create paths for each post
export async function getStaticPaths() {
  const postFilePaths = fs
    .readdirSync(POSTS_PATH)
    .filter((path) => /\.mdx?$/.test(path));

  const paths = postFilePaths.map((filePath) => ({
    params: {
      slug: filePath.replace(/\.mdx?$/, ''),
    },
  }));

  return {
    paths,
    fallback: false,
  };
}

Optimize SEO with next/head

Having a blog that nobody can find is like not having one at all. Next.js provides the Head component (from next/head) to help you easily manage <head> tags in HTML. This includes the title, meta description, and important Open Graph tags for social media. This is a crucial factor for search engine optimization (SEO).


import Head from 'next/head';

export default function PostPage({ post }) {
  return (
    <>
      <Head>
        <title>{post.title} | My blog</title>
        <meta name="description" content={post.excerpt} />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:image" content={post.image || '/default-og-image.jpg'} />
        <meta property="og:type" content="article" />
        <!-- Add other meta tags for Twitter Card, canonical link... -->
      </Head>
      <!-- Post content -->
    </>
  );
}

Optimize Images with next/image

High-quality images are an essential part of any blog, but they are also a primary cause of slow website loading. The Image component in Next.js (part of the next/image library) will automatically optimize images for you, including:

  • Automatically resizing images based on screen size.
  • Converting to modern formats like WebP.
  • Lazy loading (only loading images when they appear on screen).

import Image from 'next/image';

export default function MyComponent() {
  return (
    <Image
      src="/images/my-blog-post-image.jpg"
      alt="Description of the blog post image"
      width={800} // Original image dimensions
      height={450}
      layout="responsive" // To make the image automatically scale with the container width
      priority // Load immediately if it's the main image on the first screen
    />
  );
}

Test Coverage: A Hard-Earned Lesson from Production

Hey, listen to my experience. I once refactored a massive codebase of up to 50,000 lines of code, and the biggest lesson learned was: you must have good test coverage from the start. Sounds a bit ‘overkill’ for a personal blog, doesn’t it?

But this is the mindset I want you to grasp. Even with a small blog, writing tests for data processing functions (e.g., parse Markdown, generate RSS feed) or core components will give you much more confidence when adding new features or changing libraries. It’s like a protective charm, helping you sleep soundly instead of staying up until 2 AM fixing trivial bugs.

You can consider using Jest and React Testing Library to test React components. Or, more simply, write basic unit tests for your utility functions.

Practical Tips: To Make Your Blog Even ‘Better’

Once the basic elements are complete, here are a few personal ‘tips’ I often apply to elevate my blog, transforming it from a regular website into a more interesting and manageable space.

Customize Fonts and Icons

A website’s interface becomes more soulful and personalized when you use a unique set of fonts. Google Fonts is a vast repository with thousands of free options. You can easily import them into your globals.css file or use @next/font (if using Next.js 13+’s App Router). With Pages Router, manual import remains a common approach. For icons, Heroicons or Font Awesome are always excellent choices.


/* styles/globals.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');

html, body {
  font-family: 'Inter', sans-serif;
}

Integrate a Comments System

For a blog to truly come alive, reader interaction is indispensable. I often integrate comment systems like Disqus or Utterances (if you want to leverage GitHub Issues as a backend). Integrating them into Next.js is very simple:


// components/Comments.tsx
import { useEffect, useRef } from 'react';

const Comments = () => {
  const commentBox = useRef(null);

  useEffect(() => {
    const scriptEl = document.createElement('script');
    scriptEl.setAttribute('src', 'https://utteranc.es/client.js');
    scriptEl.setAttribute('repo', 'YOUR_GITHUB_USERNAME/YOUR_REPO_NAME'); // Replace with your repo
    scriptEl.setAttribute('issue-term', 'pathname');
    scriptEl.setAttribute('label', 'comment');
    scriptEl.setAttribute('theme', 'github-light');
    scriptEl.setAttribute('crossorigin', 'anonymous');
    scriptEl.async = true;

    commentBox.current.appendChild(scriptEl);
  }, []);

  return (
    <div className="mt-10 pt-10 border-t border-gray-200">
      <div ref={commentBox} />
    </div>
  );
};

export default Comments;

Performance Analysis with Lighthouse

Open Chrome DevTools, switch to the Lighthouse tab, and run an audit. Although Next.js and Vercel do a great job optimizing performance, you can still find areas for improvement. Typical examples include optimizing images that don’t yet use next/image or removing unnecessary CSS/JS files. This can help you increase your Lighthouse score from 90 to 98+.

Using GitHub Actions for Advanced CI/CD (Optional)

Vercel already provides quite good automatic CI/CD capabilities. However, if you want more control (such as running tests before deployment or checking code style), you can set up GitHub Actions. Although you might have read my article CI/CD with GitHub Actions from Scratch Guide and know it’s not overly complex, this could be considered ‘over-engineering’ for a personal blog without special requirements. But if you want to practice, this is a great playground to explore.

Finally, building a personal blog is an exciting journey. Don’t be too rigid; keep experimenting and learning from the ‘bugs’ you encounter. I wish you a truly satisfactory blog!

Share: