Welcome to a behind-the-scenes tour of my blog’s technical architecture! My goal with this post is to share the choices I’ve made, the custom features I’ve implemented, and why I believe this setup provides a fantastic experience for both me as a developer and you as a reader.
Core Technologies
At the heart of this blog are a few key technologies that work together to deliver a fast, modern, and maintainable website.
- Astro: I chose Astro for its incredible performance and content-first approach. Astro allows me to build most of the site with static HTML, shipping minimal JavaScript by default. This results in lightning-fast load times and a great SEO foundation. Its component-based architecture and seamless integration with various UI frameworks (though I primarily use its own
.astrocomponents) make development a joy. - Tailwind CSS: For styling, I’ve embraced Tailwind CSS. Its utility-first methodology allows for rapid UI development and highly customizable designs without writing a lot of custom CSS. It keeps my styling consistent and easy to manage.
- Markdown: All blog posts, including this one, are written in Markdown. Astro’s content collections make it incredibly easy to manage Markdown files, parse frontmatter, and render them as part of the site.
- TypeScript: To ensure code quality and catch errors early, the entire project is written in TypeScript. This brings type safety to my Astro components, utility functions, and any client-side scripts.
While many of the features are custom-built, the blog’s development started with the “Astro Paper” theme by Sat Naing (https://github.com/satnaing/astro-paper). This theme provided a solid and well-structured foundation, especially for the basic blog functionalities and styling. On top of this base, I’ve layered custom features such as the interactive resume page with PDF download, an advanced table of contents component, and various specific UI/UX enhancements to tailor the site to my needs.
Theme and Styling
I wanted a unique look and feel for the blog, so I opted to build a custom theme from scratch rather than using an off-the-shelf solution.
Custom-Built Theme
Every aspect of the visual design, from typography to color schemes, has been carefully considered and implemented using Tailwind CSS.
Light & Dark Mode
A crucial feature of modern web design is a robust light and dark mode. Here’s how it’s implemented:
public/toggle-theme.js: This vanilla JavaScript file is responsible for managing the theme. It runs before the main page content loads to prevent any flash of incorrect theme (FOUC).// Simplified snippet from public/toggle-theme.js const theme = (() => { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { return localStorage.getItem('theme'); } if (window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; })(); if (theme === 'light') { document.documentElement.setAttribute('data-theme', 'light'); } else { document.documentElement.setAttribute('data-theme', 'dark'); } // ... logic to update theme-color meta tag and handle button clickslocalStorageand System Preference: The script first checks if a user has previously selected a theme and stored it inlocalStorage. If not, it respects the user’s operating system preference viaprefers-color-scheme.- Dynamic Updates: The script sets a
data-themeattribute on the<html>element (lightordark). Tailwind CSS then uses this attribute to apply the correct styles (e.g.,dark:bg-gray-900). Thetheme-colormeta tag is also dynamically updated to ensure the browser UI matches the theme.
Tailwind Configuration
tailwind.config.cjs: This file defines my custom theme settings for Tailwind, including colors, fonts, and any custom utility classes.src/styles/base.css: This file contains base styles, custom CSS variables (e.g., for colors used in both light and dark themes), and any global styles that aren’t easily achievable with utility classes alone.
Theme-Aware Syntax Highlighting
Even code blocks adapt to the current theme! Astro uses Shiki for syntax highlighting. The toggle-theme.js script includes logic to dynamically adjust Shiki’s styling. For dark mode, I provide a custom color map (githubDarkColorMap) to ensure code is readable and aesthetically pleasing.
// Conceptual snippet from toggle-theme.js related to Shiki
function updateShikiTheme(theme) {
const shikiBlocks = document.querySelectorAll('pre.shiki');
if (theme === 'dark') {
// Apply dark theme styles, potentially by adding/removing classes
// or directly manipulating style properties based on githubDarkColorMap
} else {
// Apply light theme styles
}
}
Content Management & Structure
Astro’s content collections are the backbone of how blog posts and other content are managed.
src/content/blog/: This directory is where all Markdown blog posts reside. Each.mdfile contains frontmatter (like title, date, tags) and the post content.- Directory Structure:
src/pages/: Contains Astro components that define the routes for the site (e.g.,index.astro,about.astro,resume.astro).src/layouts/: Holds layout components that define the common structure for different types of pages.src/components/: A collection of reusable UI components (Astro, React, or Svelte components).
Key Layouts
Layout.astro: This is the global wrapper for almost every page. It includes the<html>,<head>(with meta tags, links to stylesheets, etc.), and<body>tags. It also often includes the main header and footer.Main.astro: A common layout for standard content pages, providing a consistent structure for elements like page titles and content areas.PostDetails.astro: Specifically designed for rendering individual blog posts. It handles displaying the post title, date, content (parsed from Markdown), and potentially related posts or a table of contents.Posts.astro: Used for the main blog listing page, displaying a collection of post previews or cards.
Spotlight on Custom Features
Beyond the core setup, I’ve implemented several custom features to enhance the blog.
Dynamic Open Graph Image Generation
- File:
src/utils/generateOgImages.tsx - Libraries:
satori: Converts HTML/JSX-like structures into SVGs.@resvg/resvg-js: Converts SVGs to PNG images.
- Process:
- Custom fonts (like those used on the site) are fetched.
- React components (
postOgImage.tsxandsiteOgImage.tsx) serve as templates for the OG images. - For each blog post,
satorirenders thepostOgImagetemplate with the post’s title and other details into an SVG. @resvg/resvg-jsthen converts this SVG into a PNG image.- These images are automatically generated at build time.
- Benefit: This provides automated, consistently branded Open Graph images for social sharing, improving the visual appeal of links to my content.
// Simplified conceptual example from src/utils/generateOgImages.tsx
// (Actual implementation involves Astro's build hooks)
// import { satori } from 'satori';
// import { Resvg } from '@resvg/resvg-js';
// import { siteOgImage } from './siteOgImage'; // A React component
// async function generateImage() {
// const svg = await satori(
// siteOgImage({ title: 'My Awesome Blog Post' }),
// {
// width: 1200,
// height: 630,
// fonts: [/* font data */],
// }
// );
// const resvg = new Resvg(svg);
// const pngData = resvg.render();
// const pngBuffer = pngData.asPng();
// // fs.writeFileSync(..., pngBuffer);
// }
Interactive Resume with PDF Generation
- Data Source:
src/data/resume.jsonstores my resume information in a structured format. - Frontend: The
/resumepage (likelysrc/pages/resume.astrousingsrc/layouts/ResumeLayout.astro) dynamically renders this data into an interactive HTML resume. - PDF Generation: The
public/resume-pdf-generator.jsscript uses thejsPDFlibrary. When a user clicks the “Download PDF” button:- It takes the content of the HTML resume.
- Crucially, it checks the current theme (
data-themeattribute). - It then applies theme-specific styling (colors, fonts) to the PDF, ensuring the downloaded PDF matches the website’s appearance (light or dark mode). This often involves mapping Tailwind classes or CSS variables to
jsPDFstyling options.
Reusable Astro Components
The src/components/ directory is filled with reusable Astro components that promote a modular and maintainable UI. Examples include:
Card.tsx: For displaying previews of blog posts or projects.Header.astro: The main site navigation.Footer.astro: The site footer.TableOfContents.astro: Automatically generates a TOC for blog posts based on headings.
Helper Utilities
The src/utils/ directory houses various helper functions to keep the codebase organized:
getSortedPosts.ts: Fetches and sorts blog posts by date.slugify.ts: Generates URL-friendly slugs from strings.- Other utilities for date formatting, text manipulation, etc.
SEO and Performance
Search engine optimization and site performance are top priorities.
SEO Best Practices
- Comprehensive Meta Tags:
src/layouts/Layout.astroensures that every page has appropriate meta tags (title, description, canonical URL, Open Graph tags, Twitter card tags). - JSON-LD Structured Data: Also in
Layout.astro, I include JSON-LD scripts to provide search engines with structured data about the website and its content, improving search result appearance.
Performance Optimizations
- Optimized Google Fonts Loading: Fonts are loaded efficiently, often using
font-display: swapand preconnect hints. - Preloading Critical Scripts: Important JavaScript files, like
toggle-theme.js, are preloaded to ensure they execute as early as possible. - Astro’s Built-in Features: Astro automatically handles many performance optimizations like partial hydration (only shipping JS for interactive components), code splitting, and asset optimization.
- Vite Build Optimizations: As Astro uses Vite under the hood,
astro.config.mjs(or.ts) can be configured for further build optimizations, such as:- Minification: HTML, CSS, and JavaScript are minified.
- Code Splitting: JavaScript is split into smaller chunks, improving initial load time.
- Chunk Optimization: Vite optimizes how chunks are created and loaded.
// Example snippet from astro.config.mjs (or .ts) for Vite options
// import { defineConfig } from 'astro/config';
// export default defineConfig({
// vite: {
// build: {
// rollupOptions: {
// output: {
// manualChunks(id) {
// if (id.includes('node_modules')) {
// // Group vendor modules into a separate chunk
// return 'vendor';
// }
// }
// }
// }
// }
// }
// });
Hosting and Deployment
The blog is hosted on Cloudflare Pages, which provides a seamless and robust platform for deploying static sites. The deployment process is tightly integrated with my Git workflow, making it highly efficient:
- Preview Deployments: Whenever a new feature branch is created and pushed to the GitHub repository, Cloudflare Pages automatically builds and deploys a preview version of the site. This preview deployment has its own unique URL, allowing me to test changes in a live environment before merging them. Any subsequent pushes to that feature branch will update the corresponding preview deployment.
- Production Deployment: Once a feature is complete and tested, the feature branch is merged into the
mainbranch (typically via a Merge Request or Pull Request). This merge automatically triggers a new build and deployment to the production URL.
This CI/CD (Continuous Integration/Continuous Deployment) setup offers several benefits:
- Easy Previews: I can easily share preview links with others for feedback or review changes myself before they go live.
- Automated Production Deploys: The process of deploying to production is fully automated, reducing the risk of manual errors and ensuring that the live site is always up-to-date with the
mainbranch. - Rollbacks: Cloudflare Pages keeps a history of deployments, making it easy to roll back to a previous version if needed.
Development Experience
A smooth development workflow is essential for productivity and code quality.
- ESLint & Prettier: These tools are integrated to enforce consistent code style and catch potential errors early.
- Husky: Git hooks are managed with Husky to automatically run linters and formatters before commits, ensuring code quality is maintained.
- TinaCMS (Optional): If
tina/config.tsis present and configured, it indicates an integration with TinaCMS, a headless CMS that can provide a more visual editing experience for Markdown content. (Note: If not used, this part can be omitted).
Step-by-Step Deployment Guide
This guide will walk you through deploying this Astro-based blog using Cloudflare Pages, leveraging its seamless integration with GitHub.
I. Prerequisites:
- GitHub Account: Your project code should be hosted in a GitHub repository.
- Cloudflare Account: You’ll need a Cloudflare account (a free account is sufficient for Pages).
- Node.js and npm: Ensure Node.js (which includes npm) is installed locally if you need to run or build the project outside of Cloudflare’s environment. The specific version can often be found in
.nvmrcorpackage.json(e.g., Node v18 or later). - Code Pushed to GitHub: Make sure your latest code, including all Astro configurations, is pushed to your GitHub repository.
II. Setting Up Cloudflare Pages:
- Log in to Cloudflare: Navigate to your Cloudflare dashboard.
- Select Pages: In the sidebar, go to “Workers & Pages” and then select “Pages”.
- Create a New Project: Click on “Create a project” and choose “Connect to Git”.
- Connect to GitHub:
- Authorize Cloudflare to access your GitHub repositories. You can choose to give access to all repositories or select specific ones.
- Select the repository for your blog.
- Configure Build Settings:
- Project Name: This will be part of your
*.pages.devsubdomain (e.g.,my-blog). - Production Branch: Select your main branch (usually
mainormaster). - Framework Preset: Choose “Astro” from the dropdown. Cloudflare will often auto-detect this.
- Build Command: This should be pre-filled by the Astro preset. Common commands are:
npm run build(if yourpackage.jsonhas abuildscript like"build": "astro build")- Or directly
astro build
- Build Output Directory: This should also be pre-filled. For Astro, it’s
dist/. - Root Directory (Advanced): Leave this as is unless your Astro project is in a subdirectory of your repository.
- Environment Variables (Advanced):
- This is where you can add any necessary environment variables. For example, if your
src/config.tsor other parts of your application rely on environment variables (e.g.,API_KEY,ALGOLIA_SEARCH_KEY), add them here. - Click “Add variable” for each one. You can set different values for “Production” and “Preview” environments if needed.
- Common variables for Astro projects might include those for analytics services or image optimization plugins, if not already hardcoded or managed via Astro’s config files.
- This is where you can add any necessary environment variables. For example, if your
- Project Name: This will be part of your
- Save and Deploy:
- Click “Save and Deploy”. Cloudflare will pull your code, build it according to your settings, and deploy it.
- The first deployment might take a few minutes. You can watch the progress in the Cloudflare dashboard.
III. The Build and Deployment Process:
- How it Works: When you push changes to your configured production branch (e.g.,
main), or when a pull request is created/updated against it, Cloudflare Pages automatically triggers a new build. - Astro Build: Cloudflare’s build environment will install your project dependencies (from
package-lock.json) and then run the specified build command (e.g.,astro build). This command compiles your Astro project, including Markdown files, components, and assets, into a static set of HTML, CSS, and JavaScript files in thedist/directory. - Atomic Deployments: Cloudflare deploys your site atomically, meaning the new version is fully uploaded before it becomes live, ensuring no downtime or broken states during updates.
IV. Automatic Preview and Production Deployments:
- Production Deployments: Every push to your designated production branch (e.g.,
main) will automatically trigger a new build and deployment to your main site URL (e.g.,your-project.pages.devor your custom domain). - Preview Deployments:
- When you open a pull request (or push new commits to an existing pull request’s branch) against your production branch on GitHub, Cloudflare Pages automatically builds and deploys a preview version of your site.
- Each preview deployment gets a unique URL (e.g.,
unique-id.your-project.pages.dev). - This is incredibly useful for testing changes, getting feedback, and running checks before merging to production.
- Cloudflare often comments on the pull request with a link to the preview deployment.
V. Custom Domains (Optional):
- Navigate to Custom Domains: In your Cloudflare Pages project dashboard, go to the “Custom domains” tab.
- Set up a Custom Domain: Click “Set up a custom domain” and follow the instructions.
- If your domain is already managed by Cloudflare (i.e., you use Cloudflare as your DNS provider), this process is very straightforward. You’ll typically just need to add a CNAME record.
- If your domain is hosted elsewhere, Cloudflare will provide instructions on what DNS records (usually CNAME or A records) to set up with your domain registrar or DNS provider.
- Cloudflare will automatically handle SSL/TLS certificates for your custom domain, providing HTTPS.
VI. Troubleshooting Common Issues:
- Build Failures:
- Check the deployment logs in your Cloudflare Pages dashboard. Logs provide detailed output from the build process, including any errors.
- Dependency Issues: Ensure all dependencies are correctly listed in
package.jsonandpackage-lock.jsonis up to date. - Build Command Errors: Verify your build command is correct and works locally.
- Environment Variables: Double-check that all required environment variables are set in Cloudflare Pages settings and that their names match what your code expects.
- Node.js Version: If your project requires a specific Node.js version, you can set the
NODE_VERSIONenvironment variable in Cloudflare’s build settings (e.g.,18or20).
- Incorrect Output Directory: Ensure the “Build output directory” is set to
dist/. - Asset Not Found (404 errors):
- Check your Astro project’s
baseconfiguration inastro.config.tsif you’re deploying to a subdirectory (though this is less common with Cloudflare Pages direct integration). - Verify that assets are correctly referenced in your code.
- Check your Astro project’s
- Custom Domain Issues:
- Ensure DNS records are correctly configured and have propagated. DNS propagation can sometimes take time.
- Check SSL/TLS certificate status in Cloudflare.
This detailed guide should help users successfully deploy their version of the blog. Remember to replace placeholders like your-project with actual project names or examples.
Conclusion
This deep dive has covered the main technical aspects of my blog’s setup. The combination of Astro’s performance, Tailwind’s styling flexibility, TypeScript’s safety, and a host of custom features creates a platform that I’m truly proud of. It’s a testament to how modern web technologies can be leveraged to build highly personalized and efficient online experiences.
I believe this setup not only provides a fast and enjoyable experience for you, the reader, but also a productive and maintainable environment for me as the developer.
Do you have any questions about this setup? Or perhaps you have your own interesting blog architecture to share? Feel free to reach out or leave a comment!