Lighting Up GitHub Embeds in My Eleventy Blog

For the longest time, sharing code from GitHub meant screenshotting or pasting raw snippets into Markdown. Both options felt brittle—screenshots hide the text from RSS readers, while copy-pastes drift out of sync the minute the upstream file changes. I wanted the readability of Emgithub, the SEO of server-side rendering, and zero third-party JavaScript.

That mix finally landed this week: a {% github %} shortcode that fetches code at build time, highlights it, numbers each line, and offers a copy button. All it needs is the GitHub blob URL and an optional style flag for light or dark chrome.

Goals and Constraints

I set a few guardrails before having GPT Codex write any code:

  • Stay build-time friendly. Everything should be resolved during the Eleventy build so the rendered HTML already contains the code (great for search engines, RSS, and offline readers).
  • Cache remote requests. Fetching raw GitHub files repeatedly would be slow, so EleventyFetch handles caching for me.
  • Keep the authoring experience simple. The shortcode should accept the familiar GitHub “blob” URL, including the optional #L10-L42 hash for ranges.
  • Match the site’s look. I leaned on Emgithub’s visual language—file metadata up top, code lines below—and added my own CSS to blend with the Subspace themes.

Parsing the GitHub URL

The first building block is a little parser that breaks down GitHub blob URLs into something we can work with. It extracts the user, repo, branch, file path, and any line range expressed in the hash.

		
  1. const parseBlobUrl = (githubBlobUrl) => {
  2. const url = new URL(githubBlobUrl);
  3. const parts = url.pathname.split('/').filter(Boolean);
  4. if (parts[2] !== 'blob') throw new Error('URL must be a GitHub blob URL');
  5. const [user, repo, , branch, ...fileParts] = parts;
  6. const filePath = fileParts.join('/');
  7. const rangeHash = (url.hash || '').replace(/^#/, '');
  8. let start = null;
  9. let end = null;
  10. if (rangeHash.startsWith('L')) {
  11. const [first, last] = rangeHash
  12. .split('-')
  13. .map((part) => part.replace(/^L/, ''));
  14. start = parseInt(first, 10);
  15. end = last ? parseInt(last, 10) : start;
  16. }
  17. const raw = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filePath}`;
  18. return {
  19. user,
  20. repo,
  21. branch,
  22. filePath,
  23. raw,
  24. web: githubBlobUrl,
  25. start,
  26. end,
  27. };
  28. };

If the hash is missing, the shortcode simply renders the entire file. Passing a URL that includes #L8-L25 limits the render to those lines, and GitHub’s own permalink UI makes grabbing that URL trivial.

Fetching and Highlighting at Build Time

With the metadata in hand, EleventyFetch grabs the raw file contents during the build. The result is cached for 24 hours so incremental builds stay fast.

		
  1. const source = typeof fetched === 'string' ? fetched : String(fetched);
  2. let code = source;
  3. if (meta.start && meta.end) {
  4. const lines = source.split('\n');
  5. code = lines.slice(meta.start - 1, meta.end).join('\n');
  6. }

Highlight.js then lights up the code. I detect the language using the file extension and fall back to plain text when highlight.js doesn’t know the syntax.

		
  1. const language = guessLanguageByExt(meta.filePath);
  2. const normalizedLanguage =
  3. typeof language === 'string'
  4. ? language.toLowerCase().replace(/[^a-z0-9-]+/g, '')
  5. : '';
  6. let highlighted;
  7. try {
  8. highlighted = hljs.highlight(code, { language }).value;
  9. } catch {
  10. highlighted = escapeHtml(code);
  11. }

Wrapping each line in an ordered list gives me semantic numbering without resorting to client-side scripts:

		
  1. const numbered = highlighted
  2. .split('\n')
  3. .map((line, index) => {
  4. const content = line.trim().length ? line : ' ';
  5. const lineNumber = (meta.start || 1) + index;
  6. const languageAttr = normalizedLanguage
  7. ? ` class="language-${normalizedLanguage}"`
  8. : '';
  9. return `<li value="${lineNumber}"><code${languageAttr}>${content}</code></li>`;
  10. })
  11. .join('\n');

Emgithub-Inspired Styling and Clipboard Support

All that HTML feeds into a container that mimics the Emgithub window, but the styles are now local to the project. A dedicated stylesheet handles light and dark themes via CSS variables, while a tiny copy.js script turns the “Copy” button into a real clipboard action.

		
  1. <div class="gh-embed gh-embed--${theme}">
  2. <div class="gh-embed__meta">
  3. <a class="gh-embed__file" href="${meta.web}" target="_blank" rel="noopener noreferrer">
  4. ${meta.filePath}
  5. </a>
  6. <div class="gh-embed__actions">
  7. <a class="gh-embed__raw" href="${meta.raw}" target="_blank" rel="noopener noreferrer">view raw</a>
  8. <button class="gh-embed__copy" data-clipboard>Copy</button>
  9. </div>
  10. </div>
  11. <pre class="gh-embed__pre hljs${languageClass}">
  12. <ol class="gh-embed__ol">${numbered}</ol>
  13. </pre>
  14. </div>`;

Clicking the button copies the rendered lines and flashes a “Copied!” label for a second so readers know it worked.

Using the Shortcode

Once everything is wired up, embedding a snippet is just a matter of pasting the URL and picking a theme modifier. The shortcode defaults to the light variant, so the style argument is entirely optional.

{% github "https://github.com/TheClooneyCollection/11ty-subspace-builder/blob/main/index.njk" %}

{% github "https://github.com/TheClooneyCollection/11ty-subspace-builder/blob/main/index.njk#L1-L11" "dark" %}

Because the HTML is rendered on the server, the code survives RSS feeds, printing, and readers with JavaScript disabled. I also plan to lean on GitHub’s “Copy permalink” button whenever I cite code, which locks the URL to a specific commit hash so posts won’t drift as repositories evolve.

Testing and Takeaways

The Eleventy build is the main test harness here. Running npm run build confirms that the shortcode can pull remote code, highlight it, and emit valid HTML without warnings. From there, it’s just a matter of opening the local preview, clicking the copy button, and skimming the output to make sure the styles line up with the theme.

I loved how small this feature felt once the pieces were in place. Eleventy’s async shortcodes made it easy to hook into build-time data fetching, and Highlight.js kept me from hand-maintaining language definitions. Most importantly, the authoring flow is as simple as pasting a GitHub link—exactly what I wanted when I sketched the idea.

If you try it out or have ideas for auto-detecting the site theme, let me know. I’d love to keep leveling up how code shows up on the Subspace Builder.