Skip to content

Repository files navigation

behitek.github.io

Personal site and portfolio for Hieu Nguyen (behitek) — AI engineer. Built with Astro 4 and TailwindCSS, statically generated and deployed to Cloudflare Workers (static assets).

🌐 Live site:behitek.com

Tech stack

  • Framework:Astro 4 (static output, no client-side framework)
  • Styling:TailwindCSS 3
  • Content: MDX blog posts + JSON project entries via Astro Content Collections
  • Language: TypeScript (strict)
  • Comments:Giscus (GitHub Discussions)
  • Analytics: Google Tag Manager
  • Hosting: Cloudflare Workers (static assets, via Workers Builds)

Getting started

Requires Node.js 20+.

npm install
npm run dev # http://localhost:4321
npm run build # type-checks (astro check) then builds to dist/
npm run preview # preview the production build

There is no separate lint/test script — npm run build running astro check is the main correctness gate.

Project structure

src/
├── components/ # Astro components (Navbar, Footer, ServiceCard, ProjectCard, BlogCard,
│ # ContactForm, image gallery, TableOfContents, Giscus, SEO, ...)
├── layouts/
│ ├── BaseLayout.astro # shared shell: head/SEO, fonts, GTM, Navbar + Footer
│ └── BlogPostLayout.astro # post chrome: TOC, reading progress, Giscus
├── pages/
│ ├── index.astro # homepage
│ ├── about.astro
│ ├── projects.astro
│ ├── blog/index.astro # listing
│ ├── blog/[...slug].astro # post pages, generated from the `blog` collection
│ ├── contact.astro # mock lead-capture form (client-side only, no backend)
│ ├── services/index.astro # Online Judge (DMOJ) service
│ ├── services/ai.astro # AI project service
│ ├── services/software.astro # full-stack software service
│ ├── 404.astro
│ └── rss.xml.ts
├── content/
│ ├── blog/ # blog posts (.mdx)
│ ├── projects/ # project entries (.json)
│ └── config.ts # content collection schemas
├── styles/global.css # Tailwind layers + design tokens/utilities
└── utils/ # constants.ts, helpers.ts
public/ # static assets: images, favicon, CNAME, .nojekyll

Design system

The site uses a brutalist/terminal-inspired look ("Modernist"): sharp corners (no border-radius except pills), 2px border-ink borders, monospace labels for nav/kickers, and a single warm red accent. All content is in Vietnamese by default.

Tokens live in tailwind.config.mjs:

bg #f3f2f2 surface #eae9e9 ink (text) #201e1d
accent #ec3013 accent-2 #e15b47 neutral 100–900 gray ramp
  • Font: Archivo (Google Fonts) for both headings and body; system monospace for code/labels.
  • Reusable classes (src/styles/global.css@layer components): .btn / .btn-primary / .btn-secondary, .card, .tag-pill, .kicker (e.g. 01 /), .blink-cursor, .section, .container.
  • There is currently no dark mode — it was removed along with the old i18n routing when the site was rebuilt from its original emerald/dark-mode Docusaurus-derived design into this one.

Content management

Adding a blog post

Add an .mdx file to src/content/blog/:

---
title: "Your Post Title"description: "Brief description"date: 2026-01-01author: "Hieu Nguyen"# optional, defaults to "Hieu Nguyen"language: "en"# "en" | "vi", defaults to "en"category: "AI/ML"tags: ["tag1", "tag2"]image: "/images/blog/your-image.jpg"draft: false
---

Drop images in public/images/blog/. The post is served at /blog/<filename-slug>. Content supports :::tip / :::warning / :::danger / :::note / :::info admonition blocks (via a custom remark-directive plugin in astro.config.mjs) in addition to standard Markdown/MDX.

Adding a project

Add a .json file to src/content/projects/:

{
"id": "project-slug",
"title": "Project Name",
"description": "Project description",
"category": "Product",
"tech": ["Python", "FastAPI", "Docker"],
"links": { "website": "https://example.com", "github": "https://github.com/...", "blog": "/blog/..." },
"featured": true,
"order": 1,
"status": "private",
"flagship": false,
"images": ["/images/project/screenshot-1.png"]
}

Exactly one project should have "flagship": true — it drives the case-study spotlight on the homepage. status (open-source | private) controls the private-project indicator on the project card.

Configuration

Giscus comments — set your repo/category IDs in src/components/Giscus.astro.

Google Tag Manager — GTM container ID is set inline in src/layouts/BaseLayout.astro.

Custom domain — set in public/CNAME.

Deployment

Pushes and PRs trigger a Cloudflare Workers Build (configured in the Cloudflare dashboard, see wrangler.jsonc), which runs npm run build and deploys dist/ as Workers static assets via wrangler deploy. There are no GitHub Actions workflows — Cloudflare's build check on PRs is the only CI gate.

To deploy manually: npm run build && npx wrangler deploy.

Contact

© 2026 Hieu Nguyen. All rights reserved.

Used by

Contributors

Languages