Better documentation for self hosting
Hello First of all, I wanted to thank you for your amazing project.
Is it possible for you to write a better documentation on how to self host this project?
I followed the steps that you've have provided and was able to deploy my project to Cloudflare, but nothing is usable. I'm getting this error while visiting "/":
{
"source": {
"level": "error",
"message": "Error: next() called multiple times\n at a (worker.js:1:690)\n at worker.js:1:839\n at worker.js:467:16470\n at async a (worker.js:1:825)\n at async worker.js:467:22107\n at async a (worker.js:1:825)\n at async worker.js:1:21923\n at async a (worker.js:1:825)\n at async worker.js:467:21007\n at async a (worker.js:1:825)",
"$cloudflare": {
"$metadata": {
"id": "01JWGTB8T3EWM---",
"type": "cf-worker",
"error": "Error: next() called multiple times\n at a (worker.js:1:690)\n at worker.js:1:839\n at worker.js:467:16470\n at async a (worker.js:1:825)\n at async worker.js:467:22107\n at async a (worker.js:1:825)\n at async worker.js:1:21923\n at async a (worker.js:1:825)\n at async worker.js:467:21007\n at async a (worker.js:1:825)",
"errorPattern": "Error: next() called multiple times\n at a (<DOMAIN>:<NUM>:<NUM>)\n at <DOMAIN>:<NUM>:<NUM>\n at <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)",
"messagePattern": "Error: next() called multiple times\n at a (<DOMAIN>:<NUM>:<NUM>)\n at <DOMAIN>:<NUM>:<NUM>\n at <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)"
}
}
},
"dataset": "cloudflare-workers",
"timestamp": "2025-05-30T14:43:12.765Z",
"$workers": {
"truncated": false,
"event": {
"request": {
"url": "https://---.workers.dev/favicon.ico",
"method": "GET",
"path": "/favicon.ico"
}
},
"outcome": "ok",
"scriptName": "fixtweet-self-hosted",
"eventType": "fetch",
"executionModel": "stateless",
"scriptVersion": {
"id": "0ab5dea2-2442-4---"
},
"requestId": "947f00a---"
},
"$metadata": {
"id": "01JWGTB8T3EWM---",
"requestId": "947f00a49----",
"trigger": "GET /favicon.ico",
"service": "fixtweet-self-hosted",
"level": "error",
"error": "Error: next() called multiple times\n at a (worker.js:1:690)\n at worker.js:1:839\n at worker.js:467:16470\n at async a (worker.js:1:825)\n at async worker.js:467:22107\n at async a (worker.js:1:825)\n at async worker.js:1:21923\n at async a (worker.js:1:825)\n at async worker.js:467:21007\n at async a (worker.js:1:825)",
"message": "Error: next() called multiple times\n at a (worker.js:1:690)\n at worker.js:1:839\n at worker.js:467:16470\n at async a (worker.js:1:825)\n at async worker.js:467:22107\n at async a (worker.js:1:825)\n at async worker.js:1:21923\n at async a (worker.js:1:825)\n at async worker.js:467:21007\n at async a (worker.js:1:825)",
"account": "336ae820---",
"type": "cf-worker",
"fingerprint": "59e7a60---",
"origin": "fetch",
"messageTemplate": "Error: next() called multiple times\n at a (<DOMAIN>:<NUM>:<NUM>)\n at <DOMAIN>:<NUM>:<NUM>\n at <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)",
"errorTemplate": "Error: next() called multiple times\n at a (<DOMAIN>:<NUM>:<NUM>)\n at <DOMAIN>:<NUM>:<NUM>\n at <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)\n at async <DOMAIN>:<NUM>:<NUM>\n at async a (<DOMAIN>:<NUM>:<NUM>)"
},
"links": []
}
If I try to generate a link preview in Telegram, I get: "Owie, you crashed FxTwitter :( This may be caused by API downtime or a new " as the preview with the same error message above in my CF logs.
My Wrangler.toml file:
name = "fixtweet-self-hosted"
account_id = "336ae8201c8a66----"
main = "./dist/worker.js"
compatibility_date = "2024-11-14"
send_metrics = false
[build]
command = "npm run build"
My .env (I have not configured other envs since I don't have the domains & I don't need them either):
STANDARD_DOMAIN_LIST = "mydomain....workers.dev"
API_HOST_LIST = "mydomain....workers.dev"
SENTRY_DSN = "---"
SENTRY_AUTH_TOKEN = "---"
SENTRY_ORG = "---"
SENTRY_PROJECT = "---"
My branding.json:
{
"zones": [
{
"name": "FxTwitter",
"default": true,
"domains": ["mydomain....workers.dev"],
"provider": "twitter",
"favicon": "https://abs.twimg.com/favicons/twitter.2.ico",
"redirect": "https://github.com/FxEmbed",
"color": "#6363ff",
"activityIcons": {
"default": "https://assets.fxembed.com/logos/fxtwitter-pride32.png",
"svg": "https://assets.fxembed.com/logos/fxtwitter.svg",
"16": "https://assets.fxembed.com/logos/fxtwitter-pride16.png",
"24": "https://assets.fxembed.com/logos/fxtwitter-pride24.png",
"32": "https://assets.fxembed.com/logos/fxtwitter-pride32.png",
"48": "https://assets.fxembed.com/logos/fxtwitter-pride48.png",
"64": "https://assets.fxembed.com/logos/fxtwitter-pride64.png"
}
}
]
}
"Owie, you crashed FxTwitter :( This may be caused by API downtime or a new bug" is recent. I was able to successfully deploy a few months ago and the worker just died recently.
I ended up solving the issues with the above error. Add return c.res.clone(); under the await next() on line 24 of caches.ts. This bug only occurs for workers.dev urls.
The new issue I run into is I've got no idea what routes I need to point at the worker. I setup a custom domain but I sense I need to point more at it.
I've learned how to fully self-host this, so I'll document my findings.
- Do the above fix if you plan on just using your
workers.devdomain. It'll most likely handle all the next steps. - In your
.envfile, set all of your domains up here. I essentially just removed any domain that wasn't a subdomain offxtwitter.comand then converted those over to my domains. In the end it looks like this:
(yes that's the domain I wanted shh) Make note of keepingSTANDARD_DOMAIN_LIST = "femboysx.com,fxembed.mja00.workers.dev" STANDARD_BSKY_DOMAIN_LIST = "" DIRECT_MEDIA_DOMAINS = "d.femboysx.com,dl.femboysx.com" TEXT_ONLY_DOMAINS = "t.femboysx.com" INSTANT_VIEW_DOMAINS = "i.femboysx.com" GALLERY_DOMAINS = "g.femboysx.com" NATIVE_MULTI_IMAGE_DOMAINS = "m.femboysx.com" OLD_EMBED_DOMAINS = "o.femboysx.com" MOSAIC_DOMAIN_LIST = "mosaic.femboysx.com,mosaic.fxtwitter.com" MOSAIC_BSKY_DOMAIN_LIST = "" GIF_TRANSCODE_DOMAIN_LIST = "gif.femboysx.com,gif.fxtwitter.com" API_HOST_LIST = "api.femboysx.com,api-canary.femboysx.com"mosaic.fxtwitter.com, we'll come back to that. - Setup your wrangler.toml file, keep the
elongatorbinding, we'll come back to that. For routes add this:
Changing your base domain and zone ID. Keep theroutes = [ { pattern = "femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "d.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "dl.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "t.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "i.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "g.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "m.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "o.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, { pattern = "api.femboysx.com", zone_id = "ZONE_ID", custom_domain = true }, ]custom_domainoption set. - Deploy with
npm run deploy. It will not work at first most likely, as we need to setup a few more things. - Setup elongator. Just copy the wrangler example and update your account ID and deploy it. Needs no domain and we do a service binding for FxEmbed to talk to it. You'll want to add some accounts following the readme, for my personal use I just did 1 account (YMMV).
- If you wanna be nice setup your own mosaic instance. I recommend not doing it in docker, mostly because it requires edits to the Dockerfile and the host the web server binds to. If you don't care/don't wanna be nice, just remove
mosaic.your-domain.comfrom theMOSAIC_DOMAIN_LIST- If you do wanna run it in docker, use my PR to get the correct Dockerfile. Then just edit
src/main.rsand change line 150 tolet addr = SocketAddr::from(([0, 0, 0, 0], port));to have it bind inside of the container correctly.
- If you do wanna run it in docker, use my PR to get the correct Dockerfile. Then just edit
- Same for fastgif although it's much easier to setup in Docker.
After all of that, you should have a working copy of FxEmbed setup on your own worker. It's kind of a pain and required a lot of tinkering. I'll probably PR some proper docs to self-hosting it (and fix Docker builds for mosaic) at some point.