Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# anhcodes.dev

Personal website and blog of **Anh Chu** — a data engineer/architect — covering portfolio, experience, and writing about Spark, Delta Lake, and Databricks.

🔗 **Live site:** https://anhcodes.dev/

## Tech stack

- **[Next.js 15](https://nextjs.org/)** (App Router) + **TypeScript**
- **[Tailwind CSS v4](https://tailwindcss.com/)** — warm editorial theme
- **Markdown** content via [`gray-matter`](https://github.com/jonschlinkert/gray-matter) + [`unified`](https://unifiedjs.com/) (remark/rehype), with [`rehype-pretty-code`](https://rehype-pretty.pages.dev/) for syntax highlighting
- **[Firebase Hosting](https://firebase.google.com/docs/hosting)** — served as a static export (site `anhcodesdev`)

## Project layout

The entire app lives in the [`web/`](web/) subdirectory.

```
.
├── firebase.json # Firebase Hosting config (serves web/out)
├── .firebaserc # Firebase project alias (anhcodesdev)
├── web/
│ ├── content/blog/ # Blog posts (Markdown + front matter)
│ ├── public/images/ # Static assets (blog covers, portfolio)
│ └── src/
│ ├── app/ # App Router routes (home, blog, resume, …)
│ ├── components/ # Section + UI components
│ ├── data/site.ts # Typed homepage/site content (edit here)
│ └── lib/posts.ts # Markdown parsing & rendering
└── .github/workflows/ # CI: build + deploy to Firebase
```

## Development

All commands run from `web/`:

```bash
cd web
npm install # first-time setup
npm run dev # dev server at http://localhost:3000 (live reload)
npm run build # static export → web/out/
npm run lint # eslint
```

There is no test suite; use `npm run build` and `npx tsc --noEmit` to catch errors.

## Editing content

- **Homepage** — edit the relevant export in [`web/src/data/site.ts`](web/src/data/site.ts) (`hero`, `about`, `experience`, `skills`, `projects`, `testimonials`, `contact`, `socials`), not the components.
- **New blog post** — add `web/content/blog/<slug>.md` with front matter (`title`, `date`, `description`, `cover`, `categories`, `tags`, `author`, `draft`). Put images in `web/public/images/single-blog/<slug>/`. Posts with `draft: true` show in dev but are hidden in production builds.
- **New project** — add an entry to the `projects` array in `web/src/data/site.ts`; thumbnails go in `web/public/images/portfolio/`.

## Deployment

The site is a **static export** (`next.config.ts` sets `output: "export"`, `images.unoptimized: true`, `trailingSlash: true`). `npm run build` emits directory-style HTML to `web/out/`.

Deployment is automated via GitHub Actions:

- **`firebase-hosting-merge.yml`** — on push to `master`, runs `npm ci && npm run build` in `web/` and deploys to the Firebase **live** channel.
- **`firebase-hosting-pull-request.yml`** — on PRs, deploys to a temporary preview channel.

To deploy manually:

```bash
cd web && npm ci && npm run build
cd .. && npx firebase-tools deploy --only hosting # requires `firebase login`
```
32 changes: 22 additions & 10 deletions deploy.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
COMMIT_MSG=$1
# echo "$COMMIT_MSG"
# updating public directory
hugo -t portio
#!/usr/bin/env bash
#
# Manual deploy for anhcodes.dev (Next.js static export → Firebase Hosting).
#
# Normally you don't need this: pushing to `master` triggers the GitHub
# Actions workflow (.github/workflows/firebase-hosting-merge.yml), which
# builds web/ and deploys to the Firebase live channel automatically.
#
# Use this script to deploy on demand from your machine.
# Requires `firebase login` (or a FIREBASE_TOKEN) with access to site
# `anhcodesdev`.
#
# Usage: ./deploy.sh
set -euo pipefail

# stage all changes
git add -A
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# commit changes
git commit -m "$COMMIT_MSG"
# Build the static export → web/out/
cd "$ROOT/web"
npm ci
npm run build

# push changes to remote
git push origin master
# Deploy to Firebase Hosting (config: firebase.json, .firebaserc)
cd "$ROOT"
npx firebase-tools deploy --only hosting
24 changes: 17 additions & 7 deletions web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import type { Metadata } from "next";
import { Fraunces, Newsreader } from "next/font/google";
import localFont from "next/font/local";
import "./globals.css";
import { SiteHeader } from "@/components/site-header";
import { SiteFooter } from "@/components/site-footer";

// Fonts are self-hosted (src/fonts/) rather than fetched from Google at
// build time — next/font/google's build-time fetch hangs in CI. Both are
// variable woff2 files (latin subset), so one file covers the weight range.

// Display serif — high-contrast, editorial headlines
const display = Fraunces({
subsets: ["latin"],
style: ["normal", "italic"],
const display = localFont({
src: [
{ path: "../fonts/fraunces-latin-normal.woff2", style: "normal" },
{ path: "../fonts/fraunces-latin-italic.woff2", style: "italic" },
],
weight: "100 900",
variable: "--font-display",
display: "swap",
});

// Body / UI serif — readable, with italics for labels
const serif = Newsreader({
subsets: ["latin"],
style: ["normal", "italic"],
const serif = localFont({
src: [
{ path: "../fonts/newsreader-latin-normal.woff2", style: "normal" },
{ path: "../fonts/newsreader-latin-italic.woff2", style: "italic" },
],
weight: "200 800",
variable: "--font-serif",
display: "swap",
});
Expand Down
Binary file added web/src/fonts/fraunces-latin-italic.woff2
Binary file not shown.
Binary file added web/src/fonts/fraunces-latin-normal.woff2
Binary file not shown.
Binary file added web/src/fonts/newsreader-latin-italic.woff2
Binary file not shown.
Binary file added web/src/fonts/newsreader-latin-normal.woff2
Binary file not shown.
Loading