Building My Personal Site with Astro (and Some Fun Bot Traffic)
Buying this domain was kind of an impulse decision.. It happened in the middle of the night, and I didn’t have a clear vision for what I wanted to do with it. I had a vague idea that I wanted a place to keep some stuff, but I wasn’t sure what. I didn’t want a heavy content management system or a massive web framework. I wanted something fast and easy to maintain.
So I sat down, thought about all the notes and things I have scattered across my various devices and accounts, took a look at a few tools, and ended up building this very site you’re reading right now. This is a post about how it all came together, some of the design decisions made along the way, and some interestingly chaotic bot traffic I’ve been seeing since the site came live.
Why I Picked Astro
This isn’t my first time around building a personal site. I’ve dabbled with various static site generators like Eleventy, Jekyll, and Gatsby over the years. For whatever reason, they never quite stuck.
This time, I added Astro, Quartz, and Hugo to my evaluation list. Quartz is fantastic for digital gardens, and Hugo is famously fast at compiling Markdown. However, I was hesitant about going with either of those. Since I wasn’t entirely sure what kind of content or interactive widgets I would want on the site, I wanted something with more flexibility.
I ended up picking Astro because it gave me the most flexibility without compromising performance. It uses an “island architecture”, meaning that it renders to static HTML by default with zero JavaScript sent to the browser. If you eventually need an interactive widget (like a search bar or an interactive canvas), you can drop in a component written in React, Vue, or Svelte. Astro then hydrates only that specific island.
The Subdomain Split: Resume & Recipes
One of the first thing I decided was keeping a digital version of my resume on the website. I was also toying with the ideas of making a website for my paintings or baking recipes. But those are all very different things, so I was unsure of how to organize them together under a single website.
So in the end, I decided on splitting the various parts of my site across custom subdomains. Main site lives at mosusieyu.com, and my resume lives at cv.mosusieyu.com (note, I wanted true subdomains, not cv.mosusieyu.com redirecting to mosusieyu.com/cv). I made this choice for a few different reasons:
- Keeping my resume on a short, dedicated subdomain just looks neat. Haha
- My other projects (e.g site for my paintings, baking recipes, etc) can have their own vibe, unique design system, layout, and styling without interfering with the others (maybe a cute cafe style layout for my recipes?).
- Keeping things separate also makes it easier to manage versions and dependencies. I can use different libraries or frameworks for different parts of my site without them conflicting with each other.
- I can also deploy them independently, which makes it easier to roll back changes if something breaks.
So, I set out to build this site with the above vision in mind. To keep things clean, I organized my project as a monorepo with a shared core. The root directory houses the primary package.json, main Astro configuration, and common UI assets like navigation bars, custom canvas backgrounds, and shared layouts. Child Astro projects (mainSite, cvSite) live inside the repository and import directly from these shared root components.
This sytem worked well, but it did introduce a few minor annoyances during local development.
When you run multiple local development servers, relative links between subdomains break. Navigating from localhost:4321 (main site) to localhost:4322 (CV site) requires environment aware path helpers rather than standard relative paths. I’m not a fan of messy workarounds, so I ended up just opening both sites side by side in my browser and testing navigation manually.
And to help me do this without pulling my hair out.. I set up pnpm scripts like dev:all and build:all in the root package.json. With one terminal command, I can build and run every subdomain at once while sharing the exact same underlying design system.
Creating the Blog and Project Sections
Building with Astro feels very natural if you already know HTML and modern JavaScript.
I started by putting together a bento box layout for the homepage. First, breaking the user interface down into reusable component blocks: header, navigation bars, tag pills, bento cards, and footer each live in their own file. In hindsight, that might have been a bit of overkill.. but I was exploring Astro’s component patterns, so why not have a little fun..?
The same approach was taken for the CV page. Each section (about, work experience, skills, education, etc) is its own separate component block.
// Sample Astro component structure for tags
interface Props {
tags?: string[];
withHref?: boolean;
}
const { tags = [], withHref = false } = Astro.props;
<div class="tags-bar">
{tags.map((tag) => (
<a
href={`/notebook?tag=${encodeURIComponent(tag)}`}
class="tag-pill"
>
#{tag}
</a>
))}
</div>
I knew early on that I wanted a data driven setup for everything on the site. Splitting the UI into reusable blocks means I only define what a “work experience” card looks like once, while storing the actual data in a clean JSON file. When I need to update my resume, that JSON file is the only thing I touch.
The blog posts and project write ups follow a similar pattern. They are written in Markdown with custom frontmatter headers. Astro parses these Markdown files at build time and automatically generates dynamic routing slugs for every post ([slug].astro).
Adding tag filtering was also pretty straightforward. Instead of generating static tag pages, I collect all unique tags from post frontmatter at build time to build the tag bar. A lightweight client side script then filters post cards instantly using data-tags attributes and syncs the selection to URL query parameters (?tag=...). That keeps filtering fast without requiring full page reloads or database calls.
Organizing Content: Project vs. Blog
Deciding how to organize all my stuff was probably the hardest part of this project.
I wanted space for technical side projects, long form blog posts, artwork, recipes, and career experience. But I wanted to avoid making the site feel cluttered or redundant. Should a side project get its own page on the sandbox, or should it just be a blog post?
In the end, I decided on:
- Project/Sandbox Page: Contains short summaries of projects with live demo links and primary screenshot banners. Links to blog post if there is one.
- Notebook/Blog Posts: Focus on the build process, detailed technical breakdowns, architectural decisions, and post mortems. Also where random things like hardware kit reviews, sheet music, shower thoughts go.
- Gallery Page: A dedicated display section for high resolution images of painting/3d prints/etc.
For artistic side projects, I initially debated listing them on the main sandbox page alongside technical projects. I ultimately decided against mixing them together, though I might still change my mind later!
Free Deployment with Cloudflare Pages
My main goal for hosting was keeping costs as close to zero as humanly possible.
To manage the subdomains cleanly, I set up a separate Cloudflare Pages project for each one and pointed them to watch their specific folder in the repository. That way, if I push an update to my CV site, Cloudflare only rebuilds cvSite. The main site stays untouched. It’s a pretty great setup. I get all the organization of a single monorepo alongside independent build triggers and deployments for each subdomain.
All this at zero infrastructure cost!
Future Plans
My next step for media storage is setting up Cloudflare R2 object storage.
Storing project images directly inside a Git repository gets heavy over time. Offloading image assets to R2 will keep the Git history lean while serving full res photos quickly.
Right now the blog posts are also stored as markdown files inside the repository. I have plans on moving them to R2 or something else as well, so I don’t have to update the repo code when I want to add a new blog post.
The Wild World of Post Launch Traffic
Have you ever checked your site logs right after pointing a new domain to a server? It’s kind of wild.
The moment your SSL certificates get logged in Certificate Transparency logs, automated internet bots descend upon your domain like clockwork. Now, my site is just plain static HTML generated by Astro—no databases, no login portals, no server-side scripts, nothing. But that doesn’t stop bots from blindly knocking on every single door anyway.. haha
Here are some of interesting ports, subdomains, and file paths I saw while looking through the logs:
Obscure Ports and Subdomains
Scanners probed random port numbers like :8443, :8080, :2082, and :2053. They also attempted to resolve non-existent subdomains like vpn1, login, ftp, fileshare, mail, and other stranger subdomains like wx18bf00139a5eee50, _._tcp.dc._msdcs., mi1anczbx2.
Hunting for Leaked .env Files
Scanners spend immense energy hunting for secret credential files. Within days of going live, bots scanned these paths thousands of times.
They tried everything:
/.env,/.env.production,/.env.local,/.env.staging/app/.env,/public/.env,/project/.env,/scripts/.env/database/.env,/dashboard/.env,/config/.env/actuator/envand/.env.backup- And of course,
/.git/HEAD
Fishing for Tools I Don’t Use
The bots also spent plenty of time fishing for arbitrary framework vulnerabilities. They were probing for GraphQL endpoints (/graphql), Spring Boot configs (/application.yml), GCP service account keys (/service-account.json), and user shell profiles (/.bashrc).
And as expected with internet traffic, there was a steady stream of requests hunting for legacy PHP scripts like /bolt.php and /wp-editor.php.
Geographic Oddities
Watching traffic by geographic region revealed some fun patterns:
- Scanners from France: They seemed deeply obsessed with WordPress. They kept probing for plugins like Contact Form 7 and WooCommerce, but always prefixed their paths with strange double slashes, like
//web/wp-includes/wlwmanifest.xmlor//wordpress/wp-includes/wlwmanifest.xml//xmlrpc.php. Scanners from other countries rarely sent double slash prefixes. - Scanners from China: Looked for hyper specific scripts and custom themes. I repeatedly logged probes for paths containing
/Alvin9999/https/fanfan1.net/daohang/,/theme/Bob-Theme-Argon/,/assets/index/kuailian/, and specific VPN assets like/assets/images/logo-cn-vpnweb.png.
Wrapping Up
Overall, building my personal website with Astro has been a super fun experience. It gave me a clean, simple home to organize my notes, showcase my side projects, and play around with new ideas.
Plus, watching automated bots scan endlessly for non-existent PHP files on a static Astro site has given me a few fun ideas for honeypots.. I think I’ll look into exporting all this log data to see what cool stuff I can build with it first. :)