Skip to main contentRıza Emre ARAS

Hello World

First of all, thank you for your interest in this post and in my blog.

This will be a space where I bring together my personal technical writing. I plan to share the projects I work on, my notes on networking and privacy technologies, AI-assisted tools, and the observations I gather along the way. My aim is to collect this scattered knowledge in an orderly and lasting form. The fact that both the content and the system that produces it are open source from end to end is part of this approach.

In this first post, I will walk you through how the blog system I built works.

How Does This Blog Work?

The architecture of the blog system rests on a file-based publishing model. Content does not live in a database; it sits inside the repository as plain files. Each post occupies its own directory under src/pages/blog/posts/<slug>/. The system consists of three layers:

  • Build Pipeline (route-sources.js). Collects content from disk and prepares it for publishing.
  • Prerender Layer (prerender.js). Turns each page into static HTML.
  • Cloudflare Worker (_worker.js). Serves the resulting output at the edge, on demand.

System Architecture and Design

The router is the single source of truth and holds only two lines for the blog. One is the /blog list page, the other is the /blog/:slug post resolver. Concrete post routes are not written into the router by hand. During the build, route-sources.js scans the disk and expands every directory under posts/ that contains a meta.json into a route. Publishing a post is therefore a matter of adding its directory. The same route map is shared by three separate consumers:

  • Route List (build_routes.js). Produces the source of the concrete addresses to be processed during prerender.
  • llms.txt Generation (generate-llms.js). Derives the machine-readable text version of each post.
  • llms.txt Concatenation (build-llms.js). Merges the text versions of all pages into a single combined document.

The prerender layer walks every route in a real browser with Playwright and produces static HTML. Output files are split per language and theme. There is no runtime dependency.

The static output is served at the edge by _worker.js. The Cloudflare Worker selects the correct HTML file based on the request's language and theme. It routes a /blog/<slug>/llms.txt request to the machine-readable text of the same post. The worker also sets the cache policy. Hashed build output, that is /assets/*.js and /assets/*.css, is permanent and cached for a long time. Document files are revalidated on every request, because they are the map that points to the current hashes.

Example Scenario

The path a post follows from start to publication is clear. First, the scaffold is generated.

This command opens the src/pages/blog/posts/hello-world/ directory through post-tool.js. The directory contains meta.json, index.mdx, and index.en.mdx. Next, the metadata and bodies are filled in. In meta.json, the title, description, and tags live under seo.tr and seo.en. The body does not use #. Headings start at the ## level, and the H1 value is produced from the seo.title field. The text is written as MDX in both languages.

The post is previewed locally.

Once the content is final, the machine-readable text is generated and the change is recorded.

Everything up to the push runs entirely by hand. After the push, everything is automatic. Every push to the main branch triggers the GitHub Actions deployment workflow. The workflow proceeds in three stages:

  1. Verification. It regenerates the llms files; if the result differs from the committed version, it stops the build.
  2. Build. With npm run prod it generates the cover images, compiles the project, extracts the routes, prerenders every page, and moves the llms files into the distribution directory.
  3. Deployment. wrangler sends the output to the Cloudflare Pages project and updates www.emrearas.com.

The diagram below shows the deployment workflow triggered by a push to the main branch.

Loading Diagram
Please wait...
Loading...

Content Management

A post's entire state is gathered in its own directory. The metadata lives in the meta.json file. The creation and update dates, the author field, and the per-language SEO information are here. The text itself is written as MDX in both languages. Several outputs are derived automatically from these two sources.

llms.txt Structure

Alongside the HTML that people read, each post carries a text version that machines read. generate-llms.js passes the MDX source through the agentic-data-builder tool and produces the llms/tr.txt and llms/en.txt files. Since the title is not present in the body, the generator adds the seo.title value to the top of the file as an H1. These files are committed. During the build, build-llms.js copies them into the distribution directory and merges all pages into a single file. The Cloudflare Worker returns this text to a request that carries the Accept: text/markdown header, or to a direct llms.txt request. This way the same address serves both people and machines.

Cover Image

Each post's Open Graph cover image is not prepared by hand. The cover generator produces a 1200x630 PNG from the title and description. This file is a build product and is linked to the og:image field. A new post's cover is created on its own during the prod build. This post's cover was produced the same way.

This post's automatically generated cover

SEO Layer

Each post page produces canonical, hreflang, Open Graph, and Twitter tags per language. Alongside these, structured data is embedded as JSON-LD. The list page carries a Blog schema, and the post page carries a BlogPosting schema.

List page (/blog):

Post page (/blog/hello-world):

License

This site and its source code are provided under the GNU Affero General Public License version 3.0 or later (AGPL-3.0-or-later).

Open Source

Source
Address
Personal Account github.com/remrearas
Organization (ARAS-Workspace) github.com/ARAS-Workspace
emrearascom-www (This Site) github.com/ARAS-Workspace/emrearascom-www