The successful builds gave me a website. The failed builds taught me how it worked.
Astro was completely new to me when I started building Field Notes.
I knew what I wanted the site to become: a fast publication built around Markdown, reusable layouts, predictable metadata, and a deployment workflow I could understand. What I did not know yet was how Astro divided those responsibilities, which code ran during the build, or how one bad assumption in a content file could stop the entire site from shipping.
That could have been a reason to choose something more familiar.
Instead, it became the reason to keep going.
A real project gave every concept a job. I was not learning components because a tutorial said they came next. I was learning them because the article layout needed to stop duplicating titles, topic pages needed routes, draft content needed to stay out of production, and a failed deployment needed an explanation.
Building the first version of Field Notes with Astro taught me the shape of the framework. The failures taught me its boundaries.
I started with the pieces I could recognize
Astro’s starter structure gave me enough familiar landmarks to begin without understanding the whole framework.
There were pages, components, layouts, styles, and Markdown files. I could recognize the role of each even if I did not yet understand Astro’s exact conventions.
That mattered because it let me make narrow changes.
Change the site title. Build it. Change a layout. Build it. Add a metadata field. Build it again.
The feedback loop was more useful than trying to memorize the framework first. When something broke immediately after one small change, the search area was small. When something worked, I had one more piece of the project I understood from experience instead of theory.
This became one of the habits I value most from the project: reduce the distance between a change and the evidence that tells me whether it worked.
That habit is not specific to web development. It is the same reason I prefer narrow infrastructure changes, small commits, and validation that tests the original outcome instead of merely checking whether a command exited successfully.
Content collections made frontmatter into a contract
At first, frontmatter looked like a convenient header at the top of a Markdown file.
Then I started using it to drive the site.
A published article needs more than a title. Field Notes uses metadata for descriptions, publication dates, categories, tags, series membership, ordering, and other presentation logic. Those values feed article pages, indexes, topic archives, related-post logic, series navigation, and feeds.
Astro’s current content-collection system supports schemas that validate the expected shape of collection entries. That turned the metadata from a loose convention into something the build could enforce.
That distinction changed how I thought about content.
If a post is missing information the rest of the site depends on, I would rather the build reject it than let the article quietly render incorrectly in five different places.
A schema error can feel annoying when I am trying to publish. In practice, it is a useful refusal. The site is telling me the content does not satisfy the contract I created for it.
The same lesson eventually shaped how I handled drafts. I did not want unpublished files merely hidden from the homepage while still entering the same public collection. I wanted them excluded from the production content path itself. I wrote more about that in How I Kept Drafts Out of Astro Production Builds.
Layouts taught me who owns presentation
One of my early mistakes was almost embarrassingly simple: an article title appeared twice.
The Markdown draft had its own top-level heading. The shared article layout also rendered the title from frontmatter. Both pieces were doing what I told them to do, and together they produced a page that looked wrong.
The fix was easy. The lesson was better.
The layout owns the repeated presentation: article title, date, metadata, navigation, table of contents, author information, and the other pieces that belong to every post. The Markdown file owns the article body.
That separation reduced duplicate work and made later changes safer. If I want to adjust how every article header renders, I change the layout once instead of editing dozens of Markdown files.
It also gave me a useful diagnostic question whenever something appeared twice or in the wrong place:
Which layer thinks it owns this output?
That question has saved more time than the original fix did.
Dynamic routes made build-time execution real
Topic pages were one of the first features that forced me beyond editing templates.
The site needed to take categories and tags, turn them into URL-safe values, and generate routes for the topics that actually existed.
In Astro’s static generation model, dynamic routes use getStaticPaths() to provide the parameter values that should become pages at build time. That sounds straightforward when reading the documentation.
It became memorable when a deployment failed with an error like this:
slugify is not defined
The helper existed in the source file. I could see it. The code looked close enough together that I expected the function to be available where it was called.
The build disagreed.
Moving the required logic into the scope available to getStaticPaths() fixed the problem, but the useful part was understanding why the failure mattered. Code that appears together in a source file is not automatically evidence that it will be available in the same way during build-time execution.
Astro’s routing documentation now makes the static-generation boundary explicit: dynamic routes in static mode must be known at build time through getStaticPaths().
After that failure, “build time” stopped being a vague framework phrase. It became a real execution boundary I knew how to look for.
The first useful error matters more than the loudest output
Cloud deployment logs can be noisy.
A build may print warnings, informational messages, generated routes, dependency notices, adapter output, and then the actual exception that stopped everything. When I was new to the stack, every unfamiliar line looked equally suspicious.
That is a bad troubleshooting model.
The better approach was the same one I use elsewhere: identify the first message that explains why the expected state could not continue.
Warnings still deserve review. They can become future problems. But a warning about an optional feature is not automatically the cause of a route failing to generate.
Once I started separating interesting output from causal output, deployment logs became far less intimidating.
The process became:
- Find the first actionable failure.
- Identify the file, route, or build phase involved.
- Reproduce locally when possible.
- Make one narrow correction.
- Run the same check again.
- Only then return to unrelated warnings.
That is ordinary troubleshooting, but learning a new framework gave me another place to practice it.
The deployment platform is part of the application
Early on, I thought of Cloudflare as the place the site went after Astro finished building it.
That mental model was too simple.
The deployment target influences the runtime, configuration, build commands, preview behavior, and which platform APIs are available. Astro’s current Cloudflare guidance distinguishes static deployment from on-demand rendering and documents the Cloudflare adapter and Wrangler workflow for projects that need Cloudflare runtime behavior.
I did not need to memorize every platform detail. I did need to stop treating deployment as an unrelated final step.
A project can work in the local development server and still fail when it reaches the environment that actually builds and serves it. That does not mean the platform is broken. It means the production environment is another part of the system that needs to be understood and tested.
That lesson is one reason I now care about deployment dry runs, narrow commits, and checking the generated result rather than only the source code.
Generated code can be plausible and still be wrong
AI helped me move much faster through unfamiliar Astro code.
It suggested components, explained errors, generated route logic, and gave me starting points for features I had never built before. That was genuinely useful.
It also created a new failure mode: code that looked polished enough to trust before I understood it.
A generated function can have clean names, sensible comments, and exactly the shape I expect while still making a bad assumption about scope, framework behavior, or the rest of the project.
The dangerous version of AI-assisted development is not obviously broken code. Obviously broken code gets rejected quickly.
The dangerous version is plausible code that I cannot explain.
That changed the standard I use before keeping generated code:
- I should know what problem the code solves.
- I should know when it runs.
- I should know what data it expects.
- I should know what failure would look like.
- I should be able to remove or modify it without starting the explanation from zero.
If I cannot do those things, the code may be useful scaffolding, but it is not yet something I want to own in production.
That became a much broader lesson in Cleaning Up an AI-Generated Site.
Small commits became part of the troubleshooting method
A narrow commit is more than neat history.
It reduces uncertainty.
If I change routing, metadata, styling, analytics, and deployment configuration together and the build fails, I have created a multi-variable troubleshooting problem for myself. If I change one structural behavior and validate it before continuing, the repository history becomes a sequence of testable decisions.
That is especially useful on a site where the main branch can trigger production deployment.
The workflow I eventually settled into was simple:
- make one coherent change;
- run the repository’s local validation;
- review the diff;
- commit with a message that describes the actual change;
- push;
- inspect the deployment result before stacking unrelated work on top.
This is slower than dumping a large batch of generated changes into the repository once.
It is much faster than debugging that batch after the assumptions become tangled together.
I learned faster because the site mattered to me
A tutorial project is easy to abandon because nothing depends on it.
Field Notes was different.
I wanted the articles online. I wanted the site to look like Eldritch IT instead of a starter template. I wanted the archive, topics, series, search, metadata, and publishing workflow to behave predictably. I wanted to be able to come back months later and still understand why the project was built this way.
That gave every failure a reason to solve it properly.
The project also created natural repetition:
- topic pages reused content metadata;
- related posts reused categories and tags;
- series navigation reused ordering metadata;
- archives reused publication dates;
- article navigation reused headings;
- publishing checks reused the same content contract.
Each new feature reinforced an earlier concept.
I was not studying Astro in isolation. I was repeatedly using the same ideas until they stopped feeling foreign.
The failures did more teaching than the successful copy-and-paste
The moments I remember most are not the features that worked on the first try.
A broken layout taught me where presentation belonged.
A content error taught me that metadata could be validated as data.
A failed topic route taught me about build-time scope.
A deployment failure taught me to distinguish causal errors from surrounding warnings.
A bad canonical configuration taught me that a page can look correct while still publishing incorrect machine-readable metadata.
Those failures were productive because the feedback loop was short enough to connect the symptom to the decision that caused it.
That is the part I would try to preserve if I started over.
What I would recommend to another beginner
Start with something small enough to finish but important enough that you care when it breaks.
Use a starter if it removes work that does not teach you anything useful yet. Change one thing at a time. Read the build log. Keep commits narrow. Look up framework behavior in the current documentation when the behavior matters. Do not keep generated code only because it looks professional.
Most importantly, do not turn framework mastery into a prerequisite for building.
There is a difference between working beyond your knowledge and working at the edge of it.
The first is guessing. The second is how learning happens.
I did not learn Astro and then build Field Notes.
I learned Astro by building Field Notes.
What I would do differently
I would define the content model and publishing checks earlier.
The visible parts of the site were more satisfying at first, but metadata, draft handling, routes, URLs, and deployment behavior were the pieces that determined whether the publication remained manageable.
I would also make the production-like build path part of the workflow from the beginning instead of treating the local development server as sufficient proof.
A browser preview can tell me whether a page looks right.
A build can tell me whether the project is structurally ready to ship.
I need both.
Related articles
- Building eldritchit.me with Astro
- How I Kept Drafts Out of Astro Production Builds
- Cleaning Up an AI-Generated Site
Current references
- Content collections — Astro Docs
- Routing — Astro Docs
- Deploy your Astro site to Cloudflare — Astro Docs
Security note
This article intentionally describes failures at the architectural and workflow level. Real deployment identifiers, account details, internal paths, repository secrets, tokens, environment-variable values, private URLs, and infrastructure-specific configuration are omitted or generalized.
AI transparency
AI assisted with Astro explanations, code suggestions, troubleshooting, documentation review, and editing this article. The implementation work, deployment failures, fixes, and conclusions described here came from building and maintaining Field Notes.