External Publication
Visit Post

Deploying a Structured, AI-Assisted Multilingual Dream Symbol Library on Cloudflare

Ana's Dev Scribbles February 23, 2026
Source

I'm building a dream journal app. You can log dreams, interpret them, browse a symbol library, and train lucid dreaming.

The initial version of the symbol library at dreamiary.com was generated using the ChatGPT API against a structured schema I designed. Each symbol follows the same schema: different interpretations depending on context (jungian, spiritual, cultural), related symbols, and cross-references. It's also my SEO funnel. People land on symbol pages, then discover the app. So far I'm not using any paid advertising, just that website and social media posts.

Hosting static data has been a journey.

I started with Cloudflare Pages because it's easy to set up, free and fast.

I created an initial landing page, privacy and terms of service.

I wrote about automating the process with GitHub actions, but you can also let Cloudflare automate your deployment when you connect to the GitHub Repo.

Cloudflare pages works well for up to 20k file big site. After that you need to switch to paid worker plan, if you want to stick with Cloudflare. Subscription starts at 5$, and look at https://developers.cloudflare.com/workers/platform/pricing/ for further math.

When I created my symbol library (1000 symbols to 33 languages), I had to switch.

I moved to Cloudflare Workers with static assets.

At this point, only way to deploy is with wrangler cli. Cloudflare stops handling the automated deploy.

Such example of wrangler configuration for worker with assets:

name = "example-website"
main = "worker.js"
compatibility_date = "2024-11-01"

routes = [
  { pattern = "example.com/*", zone_name = "example.com" },
  { pattern = "www.example.com/*", zone_name = "example.com" }
]


[assets]
directory = "./build"
binding = "ASSETS"
run_worker_first = true

This configuration defines custom domain mapping (www and root). You can't just edit Cloudflare DNS and point A and CNAME records to an ip. Either use wrangler.toml or domain settings under worker configuration.

Other part of the configuration defines how content should be served. Cloudflare will read from assets first unless you define worker should be first.

I use the worker to redirect all non-www traffic to www.

Example of worker.js:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.hostname === "example.com") {
      url.hostname = "www.example.com";
      return Response.redirect(url.toString(), 301);
    }

    return env.ASSETS.fetch(request);
  },
};

Worker+ assets worked fine until I reached cca 75k files. Then wrangler deploy started complaining with request timeouts more often. It took me a couple of attempts before I could deploy. That's because worker assets weren't meant to process 100k documents.

Now I'm using Workers with R2 object storage. So far, it's holding up.

R2 is the Cloudflare equivalent of the AWS's S3. And I use rclone to sync the files with the bucket.

You need access key id, secret access key and endpoint for rclone to work:

[r2]
type = s3
provider = Cloudflare
env_auth = true
access_key_id = <your_access_key_id>
secret_access_key = <your_secret_access_key>
endpoint = https://<your_account_number>.r2.cloudflarestorage.com
region = auto

I named my provider "r2", but you can choose another name.

You can get access credentials by going to r2 dashboard - https://dash.cloudflare.com//r2. Find Account Management, API Tokens. Create Account API Token. You want either "Admin read and write" or "Object read and write" access.

Worker and wrangler.toml are now different.

name = "example-website"
main = "worker.js"
compatibility_date = "2024-11-01"

routes = [
  { pattern = "example.com/*", zone_name = "example.com" },
  { pattern = "www.example.com/*", zone_name = "example.com" }
]

[[r2_buckets]]
binding = "DOCS" # just the environment variable which is used in worker.js
bucket_name = "yourbucket"

And my worker.js:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.hostname === "example.com") {
      url.hostname = "www.example.com";
      return Response.redirect(url.toString(), 301);
    }

    let key = url.pathname.slice(1);

    if (!key) {
      key = "index.html";
    }

    let object =
      await env.DOCS.get(key) ||
      await env.DOCS.get(key + ".html") ||
      await env.DOCS.get(key + "/index.html");

    if (!object) {
      return new Response("Not Found", { status: 404 });
    }

    const headers = new Headers();
    object.writeHttpMetadata(headers);
    headers.set("etag", object.httpEtag);
    headers.set("Cache-Control", "public, max-age=86400");

    return new Response(object.body, { headers })
  },
};

Maintaining the symbol library is its own problem.

Symbols in my library are not one-off static artifacts. When I review individual entries, I see gaps. Some explanations are shallow. Some symbols overlap. Some need better structure. I want a way to improve them continuously.

I've been reading about Model Context Protocol servers. The idea is to run an MCP server and connect AI as a kind of collaborator that can suggest edits, propose merges, flag inconsistencies and open PRs. I did the first phase, content generation, but I think this one will be more interesting...maintaining the symbol library.


Play Store https://play.google.com/store/apps/details?id=com.dreamiary App Store https://apps.apple.com/app/apple-store/id6754241370

Discussion in the ATmosphere

Loading comments...