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:
- Extreme perfection
- Minimalism without bloated visuals
- Solid SEO optimization, accessibility, and responsiveness
- Elegant details and cutting-edge web technologies
- 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.
Navigation Bar Button
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:
- Add
tabindex="0"
wherever it’s needed. Try navigating your website using only the keyboard, especially on Safari. - Add a “Skip to Main Content” button.
- Use ARIA attributes when necessary.
- 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.)