{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreienj6t6o6qjtacgfr4cz7mju74fexjvjd4vcah6sk5b7hvcawlbsq",
    "uri": "at://did:plc:nueu5rkumgo3omtdzftnx2ff/app.bsky.feed.post/3mfjbgycexvq2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiaokwupk2z6gji5yejkvfygknootcrq6nnph33pa3azwuebndhccq"
    },
    "mimeType": "image/png",
    "size": 337028
  },
  "description": "I'm building a dream journal app. You can log dreams, interpret them, browse a symbol library, and train lucid dreaming.\n\nThe 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 ",
  "path": "/how-did-i-deploy-a-large-static-site-on-cloudflare-2/",
  "publishedAt": "2026-02-23T08:49:35.000Z",
  "site": "https://www.amarjanica.com",
  "tags": [
    "dream journal app",
    "dreamiary.com",
    "Cloudflare Pages",
    "https://developers.cloudflare.com/workers/platform/pricing/",
    "https://dash.cloudflare.com/<youracccount>/r2",
    "Model Context Protocol servers",
    "https://play.google.com/store/apps/details?id=com.dreamiary",
    "https://apps.apple.com/app/apple-store/id6754241370"
  ],
  "textContent": "I'm building a dream journal app. You can log dreams, interpret them, browse a symbol library, and train lucid dreaming.\n\nThe 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.\n\n## Hosting static data has been a journey.\n\nI started with Cloudflare Pages because it's easy to set up, free and fast.\n\nI created an initial landing page, privacy and terms of service.\n\nI wrote about automating the process with GitHub actions, but you can also let Cloudflare automate your deployment when you connect to the GitHub Repo.\n\nCloudflare 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.\n\nWhen I created my symbol library (1000 symbols to 33 languages), I had to switch.\n\nI moved to **Cloudflare Workers with static assets**.\n\nAt this point, only way to deploy is with wrangler cli. Cloudflare stops handling the automated deploy.\n\nSuch example of wrangler configuration for worker with assets:\n\n\n    name = \"example-website\"\n    main = \"worker.js\"\n    compatibility_date = \"2024-11-01\"\n\n    routes = [\n      { pattern = \"example.com/*\", zone_name = \"example.com\" },\n      { pattern = \"www.example.com/*\", zone_name = \"example.com\" }\n    ]\n\n\n    [assets]\n    directory = \"./build\"\n    binding = \"ASSETS\"\n    run_worker_first = true\n\n\nThis 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.\n\nOther part of the configuration defines how content should be served. Cloudflare will read from assets first unless you define worker should be first.\n\nI use the worker to redirect all non-www traffic to www.\n\nExample of worker.js:\n\n\n    export default {\n      async fetch(request, env) {\n        const url = new URL(request.url);\n\n        if (url.hostname === \"example.com\") {\n          url.hostname = \"www.example.com\";\n          return Response.redirect(url.toString(), 301);\n        }\n\n        return env.ASSETS.fetch(request);\n      },\n    };\n\n\nWorker+ 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.\n\nNow I'm using **Workers with R2 object storage**. So far, it's holding up.\n\nR2 is the Cloudflare equivalent of the AWS's S3. And I use rclone to sync the files with the bucket.\n\nYou need access key id, secret access key and endpoint for rclone to work:\n\n\n    [r2]\n    type = s3\n    provider = Cloudflare\n    env_auth = true\n    access_key_id = <your_access_key_id>\n    secret_access_key = <your_secret_access_key>\n    endpoint = https://<your_account_number>.r2.cloudflarestorage.com\n    region = auto\n\nI named my provider \"r2\", but you can choose another name.\n\nYou can get access credentials by going to r2 dashboard - https://dash.cloudflare.com/<youracccount>/r2. Find Account Management, API Tokens. Create Account API Token. You want either \"Admin read and write\" or \"Object read and write\" access.\n\nWorker and wrangler.toml are now different.\n\n\n    name = \"example-website\"\n    main = \"worker.js\"\n    compatibility_date = \"2024-11-01\"\n\n    routes = [\n      { pattern = \"example.com/*\", zone_name = \"example.com\" },\n      { pattern = \"www.example.com/*\", zone_name = \"example.com\" }\n    ]\n\n    [[r2_buckets]]\n    binding = \"DOCS\" # just the environment variable which is used in worker.js\n    bucket_name = \"yourbucket\"\n\n\nAnd my worker.js:\n\n\n    export default {\n      async fetch(request, env) {\n        const url = new URL(request.url);\n\n        if (url.hostname === \"example.com\") {\n          url.hostname = \"www.example.com\";\n          return Response.redirect(url.toString(), 301);\n        }\n\n        let key = url.pathname.slice(1);\n\n        if (!key) {\n          key = \"index.html\";\n        }\n\n        let object =\n          await env.DOCS.get(key) ||\n          await env.DOCS.get(key + \".html\") ||\n          await env.DOCS.get(key + \"/index.html\");\n\n        if (!object) {\n          return new Response(\"Not Found\", { status: 404 });\n        }\n\n        const headers = new Headers();\n        object.writeHttpMetadata(headers);\n        headers.set(\"etag\", object.httpEtag);\n        headers.set(\"Cache-Control\", \"public, max-age=86400\");\n\n        return new Response(object.body, { headers })\n      },\n    };\n\n\n## Maintaining the symbol library is its own problem.\n\nSymbols 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.\n\nI'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.\n\n* * *\n\nPlay Store https://play.google.com/store/apps/details?id=com.dreamiary\nApp Store https://apps.apple.com/app/apple-store/id6754241370",
  "title": "Deploying a Structured, AI-Assisted Multilingual Dream Symbol Library on Cloudflare",
  "updatedAt": "2026-02-23T09:00:18.966Z"
}