Skip to content

Added urls:assets config option to serve static theme assets from CDN#27009

Merged
allouis merged 4 commits intoTryGhost:mainfrom
muratcorlu:feature/asset-cdn-url
Apr 7, 2026
Merged

Added urls:assets config option to serve static theme assets from CDN#27009
allouis merged 4 commits intoTryGhost:mainfrom
muratcorlu:feature/asset-cdn-url

Conversation

@muratcorlu
Copy link
Copy Markdown
Contributor

@muratcorlu muratcorlu commented Mar 28, 2026

Ghost already supports CDN base URLs for uploaded content (urls:image, urls:media, urls:files), but static theme assets (/assets/* and /public/*) have always been served from the origin. This adds urls:assets to complete the picture.

When urls:assets is configured, all URLs produced by the {{asset}} helper and Ghost's own public asset tags (cards.min.js, member-attribution.min.js, etc.) are rewritten to use the CDN as the base instead of the origin.

Benefits:

  • Improved page load performance: assets can load in parallel from origin domain
  • Cookie-free domain: serving assets from a separate domain means browsers don't send session cookies with every asset request, reducing request overhead
  • Easier CDN cache management: With the changes on Ghost, no need to drop CDN caches for assets.

Configuration:

  "urls": { "assets": "https://cdn.example.com/my-site" }

Theme assets resolve to:

  https://cdn.example.com/my-site/assets/css/screen.css

Public assets resolve to:

  https://cdn.example.com/my-site/public/cards.min.js

Note: when urls:assets is set, the Ghost subdirectory (if any) is not included in the asset URL. The CDN base URL is used as-is. This is aligned with current CDN url configurations for images, media and files. Installations running Ghost under a subpath (e.g. example.com/blog/) should ensure their CDN is configured to pull from the correct origin path.

Got some code for us? Awesome 🎊!

Please take a minute to explain the change you're making:

  • Why are you making it?
  • What does it do?
  • Why is this something Ghost users or developers need?

Please check your PR against these items:

  • I've read and followed the Contributor Guide
  • I've explained my change
  • I've written an automated test to prove my change works

We appreciate your contribution! 🙏

Ghost already supports CDN base URLs for uploaded content (urls:image,
urls:media, urls:files), but static theme assets (/assets/* and /public/*)
have always been served from the origin. This adds urls:assets to complete
the picture.

When urls:assets is configured, all URLs produced by the {{asset}} helper
and Ghost's own public asset tags (cards.min.js, member-attribution.min.js,
etc.) are rewritten to use the CDN as the base instead of the origin.

Benefits:
- Improved page load performance: assets can load in parallel from origin domain
- Cookie-free domain: serving assets from a separate domain means browsers
  don't send session cookies with every asset request, reducing request
  overhead
- Easier CDN cache management: With the changes on Ghost, no need to drop CDN
  caches for assets.

Configuration:

  "urls": {
    "assets": "https://cdn.example.com/my-site"
  }

Theme assets resolve to:
  https://cdn.example.com/my-site/assets/css/screen.css

Public assets resolve to:
  https://cdn.example.com/my-site/public/cards.min.js

Note: when urls:assets is set, the Ghost subdirectory (if any) is not
included in the asset URL. The CDN base URL is used as-is. This is aligned with current CDN url configurations for images, media and files.Installations
running Ghost under a subpath (e.g. example.com/blog/) should ensure their
CDN is configured to pull from the correct origin path.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 600030c2-19d0-4889-a547-5639a71ab37a

📥 Commits

Reviewing files that changed from the base of the PR and between d5fca1c and 5aeb303.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/meta/asset-url.js
  • ghost/core/test/unit/frontend/meta/asset-url.test.js

Walkthrough

The getAssetUrl() function has been modified to support asset CDN configuration. When config.get('urls:assets') is available, it constructs an absolute base URL by trimming trailing slashes and appending a forward slash. If the configuration is absent, the function preserves previous behavior by using the subdirectory-relative base. Comprehensive test coverage has been added to validate URL construction for theme and public assets when the CDN base is configured, including edge cases for trailing slashes, cache-busting parameters, anchor fragments, and subdirectory handling.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding a urls:assets config option for serving static theme assets from a CDN.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the feature, its benefits, configuration, and behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@muratcorlu
Copy link
Copy Markdown
Contributor Author

I am not sure if I need to do anything about Sonar check, since it's mentioning repeating lines on test file, which is completely normal, I think.

@muratcorlu
Copy link
Copy Markdown
Contributor Author

@JohnONolan Can someone have a look at this simple PR that opens new doors for the CDN and client-side performance of Ghost sites? Serving static assets of Ghost sites from a separate CDN domain will allow us to have better cache hit rates on CDN and better parallel download speeds on browser.

@JohnONolan JohnONolan requested a review from allouis March 31, 2026 10:21
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 1, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
3.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@allouis
Copy link
Copy Markdown
Collaborator

allouis commented Apr 1, 2026

Hey @muratcorlu have you got this setup working with your site?

This change looks like it would work, but it's unclear to me how the assets are getting into the CDN, are you having to manually upload those?

@muratcorlu
Copy link
Copy Markdown
Contributor Author

Hello @allouis,

Thanks for your review. CDN here doesn't necessarily means storage, it's more about distribution. You technically don't need to upload assets to somewhere else, but with a simple (for example) Caddy config addition to our Ghost setup like below, and by setting this urls:assets to https://cdn.example.com you can serve assets by Ghost via a separate domain.

cdn.example.com {
    @cdn_paths {
        path /assets/* /public/*
    }

    reverse_proxy @cdn_paths ghost:2368
}

I think this a simple but nice benefit. Because now dropping CDN caches for our Ghost domain will not drop caches for assets, since they are served from a separate domain. Cookies will not be attached to static asset requests, and we'll benefit from parallel requests of the browser with multiple domains.

That is the idea. 😊

@allouis allouis merged commit 8de1d06 into TryGhost:main Apr 7, 2026
36 of 37 checks passed
@muratcorlu muratcorlu deleted the feature/asset-cdn-url branch April 25, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants