Add Syntax Highlighting to Ghost (with Prism.js + CDN)

Most guides out there show you the “basic way” to add Prism.js, but I wanted something that feels smooth and doesn’t drag down performance too much. Sure, loading from an external CDN always adds a little cost, but honestly—it’s not big enough to notice. I still thought hard about how to cut down on any slowdown, and this is the final setup I ended up using.
Let’s jump right in.
Header (insert in Code Injection → Site Header)
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
That’s it. This one line simply tells the browser, “hey, get ready to grab stuff from jsDelivr.” It makes loading a bit faster.
Footer (insert in Code Injection → Site Footer)
<script>
(function () {
// Skip everything if no code blocks
var hasCode = !!document.querySelector('pre code');
if (!hasCode) return;
// Auto-add "line-numbers" class to all <pre><code>
document.querySelectorAll('pre code').forEach(function (block) {
if (block.parentElement) block.parentElement.classList.add('line-numbers');
});
// Simple dynamic CSS/JS loader
function loadCSS(href) {
return new Promise(function (resolve, reject) {
var l = document.createElement('link');
l.rel = 'stylesheet';
l.href = href;
l.onload = resolve;
l.onerror = reject;
document.head.appendChild(l);
});
}
function loadScript(src) {
return new Promise(function (resolve, reject) {
var s = document.createElement('script');
s.src = src;
s.defer = true;
s.onload = resolve;
s.onerror = reject;
document.body.appendChild(s);
});
}
// 1) Prism theme + line numbers CSS
loadCSS('https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css')
.then(function () {
return loadCSS('https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.css');
})
// 1.5) Insert override CSS *after* theme (to force line-wrap)
.then(function () {
var style = document.createElement('style');
style.textContent = `
pre[class*="language-"],
code[class*="language-"] {
white-space: pre-wrap !important; /* allow wrapping */
overflow-wrap: anywhere !important; /* break anywhere */
word-break: break-word !important; /* break long tokens */
}
pre.line-numbers { padding-left: 3.8em; position: relative; }
pre.line-numbers > code { white-space: inherit !important; }`;
document.head.appendChild(style);
})
// 2) Prism core + Autoloader + Line Numbers JS
.then(function () { return loadScript('https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js'); })
.then(function () { return loadScript('https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js'); })
.then(function () { return loadScript('https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.js'); })
// 3) Set autoloader path + highlight all code
.then(function () {
if (window.Prism && Prism.plugins && Prism.plugins.autoloader) {
Prism.plugins.autoloader.languages_path =
'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/';
}
if (window.Prism && Prism.highlightAll) Prism.highlightAll();
})
.catch(function (e) { console.warn('[Prism] load error:', e); });
})();
</script>
Why does this look different from other tutorials?
Most “how to” pages show you a bunch of <link>
and <script>
tags pasted directly into the header/footer. That works, but it loads everything whether you need it or not.
Here, the script first checks: “Do we even have code blocks on the page?” If not, nothing loads at all—zero overhead. If yes, then it loads CSS and JS in order, so nothing breaks.
For the look, I went with Prism’s Twilight theme—to me, it’s the one that feels the cleanest and nicest on my eyes. After that, I made sure to add Prism’s Line Numbers plugin (so every code block gets clear line numbers) and a custom line-wrapping override. This way, even very long lines don’t blow past the edge of the screen—they wrap neatly and stay aligned with the numbers.
The autoloader plugin means you don’t need to worry about which languages to include. Prism will fetch them on demand from the CDN. Line numbers are applied automatically to every block, no extra HTML required.