Skip to content

Commit 8de1d06

Browse files
authored
✨ Added CDN base URL support for theme and public assets
no-issue Static theme assets (/assets/*) and public assets (/public/*) were always served from the origin, even when other content types like images, media, and files already supported CDN base URLs. This meant sites couldn't offload their heaviest cacheable resources to a cookie-free CDN domain, hurting page load performance and increasing request overhead from unnecessary session cookies. Adding `urls:assets` completes the CDN story - sites can now serve all static resources from a dedicated domain, matching the existing pattern for `urls:image`, `urls:media`, and `urls:files`.
1 parent ef0e5f9 commit 8de1d06

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

ghost/core/core/frontend/meta/asset-url.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,12 @@ function getAssetUrl(assetPath, hasMinFile) {
126126
const isThemeAsset = !isPublicAsset && !assetPath.match(/^asset/);
127127

128128
// CASE: Build the output URL
129-
// Add subdirectory...
130-
let output = urlUtils.urlJoin(urlUtils.getSubdir(), '/');
129+
// If assetCdnUrl is configured, use it as the base (produces an absolute URL).
130+
// Otherwise fall back to the subdirectory-relative path.
131+
const cdnUrl = config.get('urls:assets');
132+
let output = cdnUrl
133+
? cdnUrl.replace(/\/$/, '') + '/'
134+
: urlUtils.urlJoin(urlUtils.getSubdir(), '/');
131135

132136
// Optionally add /assets/
133137
if (isThemeAsset) {

ghost/core/test/unit/frontend/meta/asset-url.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,61 @@ describe('getAssetUrl', function () {
180180
});
181181
});
182182

183+
describe('with asset CDN url configured', function () {
184+
beforeEach(function () {
185+
configUtils.set({assetHash: 'abc'});
186+
configUtils.set({'urls:assets': 'https://assets.example.com/my-site'});
187+
});
188+
189+
it('should return absolute CDN url for theme assets', function () {
190+
const testUrl = getAssetUrl('css/screen.css');
191+
assert.equal(testUrl, 'https://assets.example.com/my-site/assets/css/screen.css?v=abc');
192+
});
193+
194+
it('should return absolute CDN url for public assets', function () {
195+
const testUrl = getAssetUrl('public/cards.min.js');
196+
assert.equal(testUrl, 'https://assets.example.com/my-site/public/cards.min.js?v=abc');
197+
});
198+
199+
it('should handle trailing slash in assetCdnUrl', function () {
200+
configUtils.set({'urls:assets': 'https://assets.example.com/my-site/'});
201+
const testUrl = getAssetUrl('css/screen.css');
202+
assert.equal(testUrl, 'https://assets.example.com/my-site/assets/css/screen.css?v=abc');
203+
});
204+
205+
it('should not apply CDN url to favicon', function () {
206+
const testUrl = getAssetUrl('favicon.ico');
207+
assert.equal(testUrl, '/favicon.ico');
208+
});
209+
210+
it('should still append hash to CDN url', function () {
211+
configUtils.set({assetHash: 'xyz123'});
212+
const testUrl = getAssetUrl('js/app.js');
213+
assert.equal(testUrl, 'https://assets.example.com/my-site/assets/js/app.js?v=xyz123');
214+
});
215+
216+
it('should still handle # anchor in CDN url', function () {
217+
const testUrl = getAssetUrl('img/icons.svg#arrow-up');
218+
assert.equal(testUrl, 'https://assets.example.com/my-site/assets/img/icons.svg?v=abc#arrow-up');
219+
});
220+
221+
describe('with /blog subdirectory', function () {
222+
beforeEach(function () {
223+
configUtils.set({url: 'http://localhost:65535/blog'});
224+
});
225+
226+
it('should use CDN url as-is and drop the subdirectory', function () {
227+
const testUrl = getAssetUrl('css/screen.css');
228+
assert.equal(testUrl, 'https://assets.example.com/my-site/assets/css/screen.css?v=abc');
229+
});
230+
231+
it('should use CDN url as-is for public assets and drop the subdirectory', function () {
232+
const testUrl = getAssetUrl('public/cards.min.js');
233+
assert.equal(testUrl, 'https://assets.example.com/my-site/public/cards.min.js?v=abc');
234+
});
235+
});
236+
});
237+
183238
describe('file-based hash', function () {
184239
const fixturesPath = path.join(__dirname, '../../../utils/fixtures/themes/casper');
185240

0 commit comments

Comments
 (0)