A personal website built with Astro.
See the site in action:
This is an Astro website using islands architecture, static HTML by default and React for some specific interactive components.
- Static-first, most components are
.astrofiles to utilize server-rendered HTML - React islands, with
client:only="react"for client-side interactivity andclient:loadfor SSR with hydration - State management using
nanostoreswith@nanostores/reactfor shared state across islands
- Static site generation with Astro
- Tailwind CSS for styling
- Content collections for blog posts, now, projects, uses, scrapbook, and webrings
- Dark/light theme support
- Multilingual support
- Accessible (WCAG 2.1 AA)
- Responsive design
- RSS, Atom, JSON feeds, sitemap
- IndieWeb: h-card, webmentions, IndieAuth
- Global command bar
- Games: 2048, Tetris
- Node.js 18+
- pnpm (preferred) or npm
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm previewpublic/
├── fonts/ # Self-hosted web fonts
├── keys/ # Public PGP and SSH keys
├── robots.txt # Ask bad robots to go away politely :D
├── site.webmanifest # Manifest file
└── ... # Other static assets
src/
├── components/ # .astro (static) and .tsx (React islands)
│ ├── blog/ # Blog components
│ ├── home/ # Homepage components
│ ├── game/ # Game components
│ ├── layout/ # Site layout components
│ ├── KeyboardShortcut.astro # Keyboard shortcut helper component
│ └── T.astro # Translation component
├── content/ # Astro content collections
│ ├── blog/ # Blog posts, in Markdown
│ ├── now/ # Now entries, in JSON
│ ├── projects/ # Project entries, in JSON
│ ├── scrapbook/ # Scrapbook entries, in JSON
│ ├── uses/ # Uses page categories, in JSON
│ ├── webrings/ # Webring membership data, in JSON
│ └── config.ts # Collection schemas and exported types
├── hooks/ # Hooks
│ ├── game/ # Game hooks
│ └── index.ts # Other hooks
├── i18n/ # Translations and language utilities
│ ├── translations/ # JSON translation files (one per locale)
│ ├── client.ts # Client-side language utilities
│ ├── index.ts # Server-side i18n utilities
│ └── routing.ts # Locale routing helpers
├── layouts/
│ ├── BaseLayout.astro # Main site layout
│ └── RetroLayout.astro # Layout for browser compatibility pages
├── lib/ # Shared utilities and constants
│ ├── utils/ # General utility functions
│ ├── constants.ts # Site metadata, author info, service config
│ ├── feed.ts # RSS/Atom/JSON feed helpers
│ └── now-utils.ts # Helpers for the /now page
├── pages/ # File-based routing
│ ├── [locale]/ # Localised layout catch-all
│ ├── blog/ # Blog pages
│ ├── api/ # API routes
│ ├── ... # Other pages
│ ├── atom.xml.ts # Atom feed
│ ├── feed.json.ts # JSON feed
│ ├── rss.xml.ts # RSS feed
│ ├── sitemap.xml.ts # Sitemap
│ ├── sitemap-index.xml.ts
│ └── index.astro # Homepage
└── styles/ # Global styling
Create a new Markdown file in src/content/blog/ with the following frontmatter:
---
title: "Post title" # Required
date: "2025-01-08" # Required
updated: "2025-01-10" # Optional
excerpt: "Brief description" # Optional
language: "en" # Default: "en"
mood: "optimistic" # Default: "contemplative"
catApproved: true # Default: true
readingTime: 5 # Optional, in minutes
tags: ["tag1", "tag2"] # Optional array
category: "coding" # Optional
draft: false # Default: false
alternates: # Optional, for multilingual posts
- language: vi
slug: vietnamese-article
---
Your content goes here.File naming: src/content/blog/YYYY/MM/slug.md or src/content/blog/slug.md
The site supports dark and light modes:
- Dark mode is the default
- Theme preference stored in
localStorage - CSS variables in
:root/.light/.darkselectors (seesrc/styles/globals.css) - Theme applied via
classon<html>element - Respects user's system preference on first visit
12 languages supported: English, Tiếng Việt, Русский, Eesti, Dansk, 中文, Türkçe, Polski, Svenska, Suomi, toki pona, 漢喃
- Translations in
src/i18n/translations/*.json - Use the
T.astrocomponent for static translations - Language state managed via
languageStorenanostore - Language detection from browser or localStorage
When to use .astro vs .tsx:
- Use
.astrofor static content, layouts, pages - Use
.tsxwithclient:only="react"for interactive features (command bar, language toggle, games)
Island hydration directives:
<!-- No JS needed -->
<StaticComponent />
<!-- Interactive, skip SSR -->
<CommandBar client:only="react" />
<!-- Interactive with SSR -->
<Game2048 client:load />| Command | Action |
|---|---|
pnpm dev |
Start dev server at localhost:4321 |
pnpm build |
Build for production to ./dist/ |
pnpm preview |
Preview production build locally |
pnpm check |
Check TypeScript and Astro components |
- Deployment: Vercel (static output via
@astrojs/vercel) - IndieAuth: implemented via indieauth.com
- Webmentions: implemented via webmention.io
- Last.fm: music listening data via API
- imood and status.cafe: mood and status widgets
TL;DR: You are free to use, modify, redistribute, and sell the source code and the content on this website, provided proper attribution is given back to me. Attribution should include a link to my website (https://jarema.me).
This repository is dual-licensed.
All source code is licensed under the MIT License.
All non-code content, such as text, posts, essays, documentation, photos, videos, and other materials, is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).
These licenses do not apply to third-party libraries, assets, or content included in the project. Third-party components and assets are subject to their own respective licenses.
