Personal blog and sharing space at microbenz.in.th. Covers life, tech, gaming, and everything in between.
- Tech Stack
- Prerequisites
- Getting Started
- Available Scripts
- Writing Blog Posts
- Architecture
- Deployment
- Troubleshooting
| Layer | Technology |
|---|---|
| Framework | Astro 6 (static output) |
| Content | MDX via @astrojs/mdx |
| Styling | Tailwind CSS v4 + @tailwindcss/typography |
| Language | TypeScript (strict mode) |
| Hosting | Firebase Hosting (microbenz-in-th) |
| CI/CD | GitHub Actions |
| Analytics | Google Tag Manager + Rybbit |
| Fonts | Poppins, IBM Plex Sans Thai Looped, IBM Plex Mono |
- Node.js 18 or higher
- npm (bundled with Node.js)
No database, no backend, no environment variables required. This is a fully static site.
git clone https://github.com/MicroBenz/microbenz.in.th.git
cd microbenz.in.thnpm installnpm run devOpen http://localhost:4321 in your browser. The site hot-reloads on file changes.
| Command | Description |
|---|---|
npm run dev |
Start local dev server at localhost:4321 |
npm run build |
Build production site to ./dist/ |
npm run preview |
Preview the production build locally |
There is no test suite — this is a static site with no server-side logic to test.
Blog posts live in src/content/blog/ as numbered directories.
-
Create a new directory following the numbering convention:
src/content/blog/32-your-post-slug/ -
Add an
index.md(orindex.mdxfor JSX components) with the required frontmatter:--- title: 'Your Post Title' date: '2026-04-18T10:00:00.000Z' slug: your-post-slug featuredImage: './cover.jpg' tags: ['Tech'] --- Your content here...
-
Place the featured image (and any inline images) in the same directory alongside
index.md.
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Post title displayed as heading |
date |
ISO 8601 string | Yes | Publication date |
featuredImage |
relative path | Yes | Hero image shown on card and post |
tags |
string[] | Yes | Category tags (e.g., ['Tech', 'Life']) |
slug |
string | No | URL override; defaults to directory name |
Posts are served at /blog/<slug>. The slug field in frontmatter overrides the directory name. Legacy URLs (old Medium slugs and old blog URLs with Thai characters) are redirected in astro.config.mjs.
├── .github/
│ └── workflows/
│ ├── deploy-live.yml # Auto-deploy to prod on push to main
│ └── deploy-preview.yml # Manual preview channel deploy
├── public/
│ └── assets/
│ ├── favicon/ # Favicon files
│ ├── images/ # Site-wide images (OG image, photos)
│ └── logo/ # Brand logos
├── src/
│ ├── components/
│ │ ├── card/
│ │ │ └── BlogCard.astro # Featured and regular blog card variants
│ │ ├── BaseHead.astro # <head> with meta, OG, Twitter cards, fonts
│ │ ├── FormattedDate.astro # Locale-aware date renderer
│ │ ├── Header.astro # Fixed nav (72px) with backdrop blur
│ │ ├── HeaderLink.astro # Nav link with active-state underline
│ │ └── Footer.astro # Footer with links and copyright
│ ├── content/
│ │ └── blog/ # Blog post directories (01-ywc14/, 02-…/)
│ ├── layouts/
│ │ └── PageLayout.astro # Main layout: GTM, Rybbit, Header, Footer
│ ├── pages/
│ │ ├── index.astro # Homepage with featured + grid layout
│ │ ├── blog/
│ │ │ └── [...slug].astro # Individual post page
│ │ └── rss.xml.js # RSS feed
│ ├── styles/
│ │ └── global.css # Tailwind v4 directives + @theme tokens
│ ├── consts.ts # SITE_TITLE, SITE_DESCRIPTION
│ └── content.config.ts # Content collection schema (Zod)
├── astro.config.mjs # Astro config: integrations, redirects, Vite
├── firebase.json # Firebase Hosting config (public: "dist")
├── tailwind.config.cjs # Tailwind config (fonts, colors)
└── tsconfig.json # Strict TypeScript config
Posts are loaded via Astro's content collections API using a glob loader:
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: ({ image }) => z.object({
title: z.string(),
date: z.string().or(z.date()).transform((val) => new Date(val)),
featuredImage: image(), // processed by Astro Image for optimization
tags: z.array(z.string()),
slug: z.string().optional(),
}),
});| Route | File | Description |
|---|---|---|
/ |
src/pages/index.astro |
Homepage: hero + featured post + 3-col grid |
/blog/<slug> |
src/pages/blog/[...slug].astro |
Individual post with hero image and related posts |
/rss.xml |
src/pages/rss.xml.js |
RSS feed for all posts |
Colors and fonts are defined as CSS custom properties in src/styles/global.css using Tailwind v4's @theme syntax:
| Token | Value | Usage |
|---|---|---|
--color-space-cadet |
#173057 |
Dark navy — footer background, headings |
--color-celestial-blue |
#518ECB |
Bright blue — links, tags, accents |
--color-bittersweet |
#F26A5D |
Coral — highlights, call-to-action |
--color-main-bg |
#d9e6f2 |
Light blue — page background |
--font-sans |
Poppins | Body text |
--font-thai |
IBM Plex Sans Thai Looped | Thai language content |
--font-mono |
IBM Plex Mono | Code blocks |
Prose content (blog post body) is styled via the @tailwindcss/typography plugin with prose-lg / prose-xl classes.
astro.config.mjs contains 20+ redirect rules mapping old Medium URLs (with Thai characters) and old blog slugs to current /blog/<slug> routes. These are handled at build time as static redirects.
The site deploys to Firebase Hosting (microbenz-in-th) from the ./dist/ directory.
Push to main triggers .github/workflows/deploy-live.yml, which:
- Checks out the code
- Installs dependencies (
npm install) - Builds the site (
npm run build) - Deploys
./dist/to Firebase Hosting (live channel)
Required GitHub secret: FIREBASE_SERVICE_ACCOUNT_MICROBENZ_IN_TH
.github/workflows/deploy-preview.yml is triggered manually via workflow_dispatch. It deploys to a temporary preview channel (preview-<sha>) without affecting production — useful for reviewing changes before merging.
# Install Firebase CLI if not already installed
npm install -g firebase-tools
# Authenticate
firebase login
# Build
npm run build
# Deploy
firebase deploy --project microbenz-in-thAstro generates a fully static site — no Node.js server required. The dist/ directory contains plain HTML, CSS, JS, and optimized images ready to serve from any static host.
Error: listen EADDRINUSE: address already in use :::4321
Kill the process using port 4321 or run on a different port:
npm run dev -- --port 3000Astro processes images referenced in frontmatter (featuredImage) at build time. If an image is missing from the post directory, the build will fail with a module resolution error. Ensure the image file path in frontmatter exactly matches the filename (case-sensitive).
Check that all required frontmatter fields are present and the featuredImage path is a valid relative path starting with ./. The Zod schema in src/content.config.ts will throw a descriptive error indicating which field is missing or invalid.
This project uses Tailwind CSS v4, which scans source files automatically. If custom classes from global.css (like .eyebrow or .font-thai) don't apply, verify:
- The
@import "tailwindcss"directive is at the top ofsrc/styles/global.css global.cssis imported insrc/components/BaseHead.astro
Google Fonts are loaded via <link> tags in src/components/BaseHead.astro. This requires an internet connection. Fonts fall back to system sans-serif when offline — this is expected behavior.