A good[ish] website

Web development blog, loads of UI and JavaScript topics

Build a simple, isolated demo component with Gatsby

Filed under: Tooling— Tagged with: gatsby

Here’s how to build a really simple code demo iframe with Gatsby, which lives outside the site’s main CSS scope.

The nice thing about static site generators like Gatsby is that you can use MDX to write post, which means you can write demos right into the posts with React components. That’s great, but sometimes you want a demo that lives outside the current scope. That can be done with an iframe.

Some requirements we want our demo to meet:

  • All files should be in the same dir with the main index.mdx file.
  • The demo is just one file.
  • No build process.

The dir structure looks something like this:

├── code-demo/
│   ├── demo.html
│   └── index.mdx
├── bar-foo.mdx

Importing HTML as a string

It would be cool if Gatsby would render HTML files placed into the post dir: posts/code-demo/demo.html, we could then load them into an iframe: <iframe src='/code-demo/demo.html'/>. But it doesn’t do that, which is okay, because iframe also has the lesser known attribute srcdoc, which you can pass an HTML document as a string and it will just render it normally. Neat.

Use WebPack html-loader with Gatsby

We need to use WebPack’s html-loader to import HTML files. This is pretty easily done in Gatsby, since Gatsby is essentially a big WebPack config system.

First install it:

npm i --save-dev html-loader

Then export the onCreateWebpackConfig hook from the gatsby-node.js file, like so:

// gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
    module: {
      rules: [
          test: /\.html$/i,
          loader: 'html-loader'

Pass HTML as a string into an iframe

Our simple test demo file looks something like this, just a good old HTML file:

<!-- posts/code-demo/demo.html -->
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test demo</title>
    <style id="styles">
      #foo {
        margin-bottom: 10px;
      .hidden {
        visibility: hidden;
    <button id="foo">Button</button>
    <div id="message" class="hidden">💥</div>
  <script id="scripts">
    const button = document.getElementById('foo')
    const message = document.getElementById('message')
    button.addEventListener('click', () => message.classList.toggle('hidden'))

Then import it in the MDX post file and put the string into an iframe:

title: Code demo
date: 2021-12-28 21:26:44

import demo from './demo.html'

Here’s the demo:

<iframe srcdoc={demo} title='Code demo' />

And it renders like this:

A test demo iframe, with a simple click handler and some primitive styles

There you have it. Check my post on how to make a React iframe component.

Downsides to this approach

It’s just HTML, any imports are not resolved. The ESM import statement works in modern browsers tho, but if you try to do import foo from './foo', it looks from /foo.js, that would be the static dir, not here in the post dir. But script tags can be used like normal for sure.


I’m planing a post on more elaborate demo system. Probably publishing it within next few days.

Comments would go here, but the commenting system isn’t ready yet, sorry.

  • © 2022 Antti Hiljá
  • About
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!