惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
U
Unit 42
aimingoo的专栏
aimingoo的专栏
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
博客园_首页
M
MIT News - Artificial intelligence
博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
IT之家
IT之家
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
D
Docker
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News

jola.dev

Migrating your Bluesky account the hard way | jola.dev Cluster singleton pattern | jola.dev cove.town, atproto self-hosted self-hosting | jola.dev Self-hosting an atproto container registry | jola.dev Migrating to the new Tangled knot2 | jola.dev Self-hosting and Tangled | jola.dev Self-hosting your PDS | jola.dev Taking control of your atproto account | jola.dev No cost, no value | jola.dev Latch - an Elixir atproto OAuth library | jola.dev Limited output is a feature | jola.dev Distributed rate limiter with HRW in Elixir | jola.dev A computer can never be held accountable | jola.dev Elixir Cluster 101 | jola.dev How to stop Claude from saying load-bearing | jola.dev Let libraries be libraries | jola.dev CI workflows on Tangled for Elixir | jola.dev Automatically syncing your blog to atproto and standard.site | jola.dev Appreciation for the small web | jola.dev Treating LLMs as programming books Publishing your blog to standard.site in Elixir Generating OG images in Elixir The social contract of writing Highest Random Weight in Elixir bunnyx: a bunny.net Elixir client library Building for the joy of building Running local models on an M4 with 24GB memory How to hit your Claude weekly limit so you can go outside and touch grass Dropping Cloudflare for bunny.net Building a blog with Elixir and Phoenix
Speeding up a Phoenix LiveView web app with a CDN | jola.dev
https://jola.dev/about · 2026-08-26 · via jola.dev

I’ve written before about using bunny.net as a CDN for this blog, where I basically just cache everything, because it’s a static blog. Whenever I release a new blog post, I purge the pull zone, and the new blog post becomes available. This works great for my blog, it doesn’t have user sessions, no server side dynamic content, also crucially it doesn’t use LiveView websockets. It basically means serving my blog requires no resources on my side at all, it’s fully CDN driven.

For your average Phoenix LiveView web app this won’t work so well though. You’re very likely to have dynamic content, user sessions, and websockets. After some experimentation I found a really high impact low effort approach to using bunny.net for this use case, so here’s a little write up.

Note that there are other CDNs out there too and you should be able to use them just fine with this guide!

A CDN that doesn’t get in your way

Looking over your average Phoenix LiveView web app, the stuff you want to cache is likely being managed by the Plug.Static and Phoenix assets pipeline already. mix assets.deploy already builds your CSS and JS, as well as managing images, attaching unique hashes on each build. Since every new version produces a new hash, it means we can basically cache assets forever. The way this is hooked up, by default, is you’ve got static_paths defined in your Web module, like:

def static_paths, do: ~w(assets fonts images robots.txt)

and then you’ve got Plug.Static in your endpoint.ex.

plug Plug.Static,

at: "/",

from: :your_app,

gzip: not code_reloading?,

only: YourAppWeb.static_paths(),

raise_on_missing_only: code_reloading?

That’s neat thing number one. Number two is that you can configure the URL that static assets are served from. This makes it easy to serve the static assets through a different domain than the main one. Let’s see how we can make excellent use of that! In runtime.exs you have a block of config in the prod section like:

config :your_app, YourAppWeb.Endpoint,

url: [host: host, port: 443, scheme: "https"],

http: [

# Enable IPv6 and bind on all interfaces.

# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.

# See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0

# for details about using IPv6 vs IPv4 and loopback vs public addresses.

ip: {0, 0, 0, 0, 0, 0, 0, 0}

],

secret_key_base: secret_key_base

We’re going to add a single line here, static_url.

config :your_app, YourAppWeb.Endpoint,

url: [host: host, port: 443, scheme: "https"],

static_url: [host: "cdn.yourapp.com", scheme: "https", port: 443],

...

With that configuration set, we now have to also ensure that all assets are getting full URLs, rather than relative paths, so go through your root.html.heex and any other files that include asset URLs, and wrap them in url. You want your app.js and app.css references to look something like this.

<link phx-track-static rel="stylesheet" href={url(~p"/assets/css/app.css")} />

<script phx-track-static type="module" src={url(~p"/assets/js/app.js")}>

This means all of your static assets, like app.js and app.css will be served from cdn.yourapp.com instead of from your main host. These assets links get automatically rewritten, no extra work required, assuming they’re wrapped in url. So ~p"/images/logo.svg" does not get rewritten, but url(~p"/images/logo.svg") does.

Okay, that’s the Phoenix side work done. Let’s move on to bunny.net.

Setting up your pull zone

Log in to your account and create a new pull zone, this is the CDN configuration that will sit on cdn.yourapp.com. Name it something reasonable and under Origin type select Origin URL and input your website. The rest of the settings can be left at default, although feel free to explore. Note that pull zone names have to be globally unique. Once created, click skip instructions. You’ll land on Hostnames. Add your web site subdomain, eg cdn.yourapp.com, and follow the instructions to create the DNS record to point at the pull zone.

Once it’s active, deploy your website, and you should see app.js and app.css, as well as any other assets, being served through your CDN subdomain. This can significantly reduce the outgoing network traffic for your website, as well as speeding up page loads.

The rest of the pull zone settings can be left in their default state, but consider also activating Origin Shield, which will further reduce the traffic to your server, and play around with “Stale Cache” settings, that let the CDN serve stale assets while re-fetching.

Conclusion

I want to emphasize that you can put bunny.net directly in front of your web app, you don’t need to limit it to static resources, but the goal of this guide is to show you a low effort high reward approach that requires minimal configuration.

Also leaving a little note on websockets here. Bunny.net used to cap websockets, where you had to pay for a higher tier to get more websocket capacity. Not great for Phoenix LiveView! However, they’ve recently switched to a pay per use model, currently charging $0.235 per million connection minutes. Dirt cheap, but if all you want to do is speed up asset delivery, there’s no reason to run the websocket connections through bunny.net.

Hope this was helpful!