Skip to main content

Blog Rewired 🍀

Sun Mar 09 2025

It's Free Real Estate: Design the Perfect Blog

注意:为了面向更多的受众,本文不提供中文版本,如有需要请使用浏览器的翻译功能,感谢理解。

Please note: To reach a broader audience, a Chinese version of this article is not provided. If needed, please use your browser’s translation feature. Thank you for your understanding.

Introduction

Nowadays, With so much free “real estate” to choose from, we can literally build a complete blog with blazingly fast worldwide CDN, automated CI/CD, distributed database, edge computing API endpoints, spam protection, and LLM-based content moderation, all for free.

As a wise man (or maybe it was Cloudflare) once said:

IT’S FREE REAL ESTATE!

But how? Let’s dive into the rabbit hole. In this article, I’ll walk you through the architecture of my blog.

Overview

Tip: Click “On this page” to view the table of contents and jump to any section you want.

Here’s a brief overview of the technology stack:

  • Frontend:
    • Core: Astro.js, Svelte, Tailwind CSS, TypeScript
    • Content: Markdown, Remark, Rehype
    • Hosting: Cloudflare Pages
  • Backend:
    • Core: Cloudflare Pages Functions, TypeScript
    • Database: Cloudflare D1
    • Spam Protection: Cloudflare Turnstile
    • LLM: OpenAI SDK, ByteDance Volcano Ark
    • Notification: Telegram Bot Platform

Content Generation

Astro can do a lot of heavy lifting right out of the box. It also has a useful guide for basic Markdown rendering. If you want to build a documentation website, Astro offers the polished and easy-to-use Starlight framework. However, building a complete blog requires tackling some problems.

Styling

To style the articles, I started with Tailwind Typography. After some further developement, I quickly realized its limitations. Fortunately, Tailwind Typography can be customized with the tailwind.config.mjs file. Here is an example:

/** @type {import('tailwindcss').Config} */
import colors from 'tailwindcss/colors';
export default {
  extend: {
      typography: {
        DEFAULT: {
          css: {
            maxWidth: '100%',
            h2: {
              fontSize: '22px',
            },
            // ...
          },},
        stone: {
          css: {
            '--tw-prose-body': colors.stone[800],
            // ...
          },},},},},};

Content Excerpt

Excerpts of blog posts are useful for displaying summaries in listings and as meta descriptions. You can generate excerpts during the build process using a few techniques. I chose markdown-it and html-to-text (though the latter is not actively maintained). Here’s the helper function I use to generate excerpts:

import MarkdownIt from 'markdown-it';
import { convert } from 'html-to-text';

const parser = new MarkdownIt();
const excerptCache = new Map<string, string>();
export function createExcerpt(slug: string, body: string, maxLen: number) {
  const cached = excerptCache.get(slug);
  if (cached) {
    return cached;
  }
  const html = parser.render(body);
  const options = {
    wordwrap: null,
    selectors: [
      { selector: 'a', options: { ignoreHref: true } },
      { selector: 'img', format: 'skip' },
      { selector: 'figure', format: 'skip' },
      { selector: 'pre', format: 'skip' },
      { selector: 'table', format: 'skip' },
      { selector: 'h1', format: 'skip' },
      // ...
    ],
  };
  const text = convert(html, options);
  const distilled = convert(text, options);
  const excerpt = distilled.substring(0, maxLen) + '…';
  excerptCache.set(slug, excerpt);
  return excerpt;
}

Please note that the HTML content is converted twice to ensure thorough cleanup.

(🚧 This article is still under construction.)

User Experience

Design Principles

The aesthetics of my blog were inspired by ribice/kiss. Although this theme doesn’t quite match my standards, it remains my favorite. If you don’t want to invest the time in building a blog from scratch, it’s a great starting point.

I’m a lazy person who likes enjoying life and touching grass instead of staring at a boring computer screen for hours. So, after investigating all the obvious options, I convinced myself that no free blog website templates met these requirements:

  1. Extreme perfection
  2. Minimalism without bloated visuals
  3. Solid SEO optimization, accessibility, and responsiveness
  4. Elegant details and cutting-edge web technologies
  5. Extensible to whatever I like

Speaking of perfection, a basic element of perfection is consistency: maintaining the same padding, margin, gap, color, size, and so on throughout the design.

Components

Every good design is a human-centred design. For example, any button intended for user interaction should have a clear and sufficiently large border, ensuring that mobile users can easily identify the tappable area. Visual cues should appear when a cursor hovers over a button to encourage interaction. On the contrary, a disabled button should be easily distinguishable from its active state.

Here is the Tailwind CSS code for a button:

@layer components {
  .c-button {
    @apply truncate text-stone-500 text-sm font-light text-center py-1.5 border rounded-full border-stone-300 transition-colors duration-300;
  }
  
  .c-button:hover:not(:disabled), .c-button:focus:not(:disabled) {
    @apply border-cyan-500 text-cyan-700 cursor-pointer outline-0;
  }

  .c-button:disabled {
    @apply text-stone-400 border-dashed;
  }

  // ...
}

Speaking of hover effects, here’s a handy Tailwind CSS trick. It adds a hocus variant and allows you to target both hover and focus states simultaneously, therefore reducing code duplication.

@custom-variant hocus (&:hover, &:focus);
<a href="https://example.com" class="hocus:text-cyan-700">
  Example
</a>

Motions

Here are some notable motions used in my blog. Other motions are mostly trivial to implement using Tailwind CSS.

The hover effect of navigation bar buttons is implemented using CSS backdrop-filter. It’s in the Baseline 2024 list so don’t worry about the compatibility. You should always blame users for not upgrading their browsers. Here is the code:

<a
  tabindex="0"
  href="https://example.com"
  class="hocus:after:h-[calc(100%+0.4rem)] relative after:pointer-events-none after:absolute after:-top-[0.2rem] after:-left-[0.3rem] after:z-10 after:h-0 after:w-[calc(100%+0.6rem)] after:backdrop-invert after:transition-all after:duration-300 focus:outline-0"
>
  Example
</a>

Dynamic Item

This transition effect is used on post titles on the home page during hover and on the items in the table of contents. To make the transition buttery smooth, I added the following ingredients:

  • Blur filter
  • Size change
  • Color fade-in

In the table of contents, it also includes an X-axis translation. Here is the code:

<a
  class="relative text-stone-500 no-underline transition duration-300 before:absolute before:top-0 before:-left-3 before:inline-block before:h-0 before:w-0 before:-translate-x-1/2 before:bg-cyan-50 before:blur-xs before:transition-all before:duration-300 [&.active]:translate-x-4 [&.active]:text-cyan-700 [&.active]:before:h-full [&.active]:before:w-1 [&.active]:before:bg-cyan-500 [&.active]:before:blur-none"
  href="#example"
>
  <span class="py-1">Example</span>
</a>

“The Wiggle”

When an operation fails, the message text will turn red and display a subtle wiggle animation. This visual cue is inspired by ByteDance Acro Design. Here is the code:

@theme {
  --animate-wiggle: wiggle 150ms ease-out 2;
  @keyframes wiggle {
    0% {}
    25% {
      transform: translateX(-1px);
    }
    75% {
      transform: translateX(1px);
    }
    100% {}
  }
}
<div
  class:invisible={!message}
  class:text-red-500={isErrorMessage}
  class:motion-safe:animate-wiggle={isErrorMessage}
  class:motion-safe:animate-pulse={isSubmitting}
>
  {message}
</div>

Accessibility

An easy way to improve accessibility is by auditing your website using Lighthouse and always writing semantic HTML. However, there are some extra things to consider:

  1. Add tabindex="0" wherever it’s needed. Try navigating your website using only the keyboard, especially on Safari.
  2. Add a “Skip to Main Content” button.
  3. Use ARIA attributes when necessary.
  4. Review this checklist and test the website yourself.

Responsive Design

Tailwind CSS has concise and helpful documentation on this topic. The general idea is to target smaller screens first and then consider larger screens. I prefer to test the responsive design with Chrome DevTools because it can simulate various screen dimensions.

SEO

Similar to accessibility, SEO (Search Engine Optimization) can be audited using Lighthouse. However, since SEO is a rather broad concept that covers many aspects, I will share my approach to achieving a decent level of SEO.

Basic Metadata

Basic metadata includes the language attribute and standard HTML metadata in the head section. Here is a minimal example:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <meta name="description" content={description} />
    <title>Example</title>
    <link rel="cononical" href={Astro.url} />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <link rel="sitemap" href="/sitemap-index.xml" />
    <meta name="generator" content={Astro.generator} />
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

For Crawlers

Google’s crawler tends to prefer websites with a correct sitemap, a robots.txt file, and a well-handled 404 error page. You can configure the generation of sitemap and robots.txt file by following the guide of @astrojs/sitemap. If you’re using Google Search Console, it’s recommended to manually submit your sitemap when you add your blog for the first time.

You might also find it helpful to configure your RSS feed generation in a similar way. For example, I use @astrojs/rss and followed this guide.

Open Graph

The Open Graph protocol is used by many major social media platforms for displaying any web page as a rich object. The rich object can be a card of a link. Implementing these properties on your website can potentially increase its exposure. Here are some of the Open Graph properties I used:

<head>
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:url" content={Astro.url} />
  <meta property="og:site_name" content={config.site.name} />
  <meta property="og:type" content={pageType} />
</head>

(🚧 This article is still under construction.)

Leave a comment

0 / 560
CAPTCHA is loading...

Comments

Loading comments...