I spent a long time looking for the perfect framework for technical blogging. I wanted something that would not only let me write text, but also show the results of scripts, visualize data, and do it all beautifully. At the same time, it was important for everything to be static, so it could be hosted for free, without servers or extra hassle.

And finally, I got lucky — I came across Observable Framework. This thing does all that and even more. In this post, I’ll explain what it is, how to run it together with GitHub and Azure Static Web Apps, and how to connect PowerShell to the whole setup.

In a Nutshell: Observable Framework

Observable Framework is a static site generator by the ObservableHQ team that combines the power of interactive notebooks with the convenience of modern frontend development. Unlike typical blogging platforms, it allows you to:

You write your notes in Markdown, embed interactivity — and voilà, you’ve got a modern static site ready to be deployed on GitHub Pages or Azure Static Web Apps. All interactivity works in the browser, no backend needed. Simple, fast, elegant.

Setting Up Observable Framework

Getting started takes just a few clicks — start here. The template repo includes a devcontainer and all necessary config. Just create your own repo from the template and you’re ready to start writing posts.

A new post is just a new file, for example /src/my-new-post/index.md.

But plain Markdown is boring. We want interactive tables, charts, visualizations. And that means data. Here’s where the magic starts.

Observable Framework lets you reference executable scripts instead of plain data files. For example, this:

const allData = await FileAttachment("allData.csv").csv()

There’s no allData.csv, but there is an allData.csv.ps1. The framework will run the PowerShell script, get its output, and treat it as if it were a normal CSV file.

To make this work with PowerShell, a couple of tweaks are needed.

1. Add PowerShell to devcontainer

Update your devcontainer.json to include PowerShell:

{
  "image": "mcr.microsoft.com/devcontainers/universal:2",
  "hostRequirements": {
    "cpus": 4
  },

  // skipping stuff

  "features": {
    "ghcr.io/devcontainers/features/powershell:1": {
      "version": "latest"
    }
  }

  // skipping stuff
}

2. Register the .ps1 interpreter in observablehq.config.js

export default {

  // skipping stuff

  interpreters: {
    ".ps1": ["/usr/bin/pwsh"]
  },

  // skipping more stuff

};

This tells the framework to use pwsh to run PowerShell scripts.

More on Data Loaders — here

Example PowerShell Script

Create /src/my-new-post/data.csv.ps1:

Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/vega/vega/refs/heads/main/docs/data/movies.json' | 
    ConvertFrom-Json | 
    Select-Object -Property Title, 'Worldwide Gross', 'US Gross', 'IMDB Rating' | 
    ConvertTo-Csv

The script grabs a JSON file, filters and converts it to CSV, and writes to stdout — that’s key!

Then, in your post, just add:

Inputs.table(FileAttachment("data.csv").csv())

And boom — you’ve got an interactive data table.

Live examples from this blog:

Azure Static Web Apps

To publish, we use Azure Static Web Apps — Microsoft’s free static hosting service. It integrates with GitHub effortlessly: connect your repo, specify the build folder (dist) — and you’re done. If you give Azure access to your GitHub account, it will automatically create a GitHub Actions workflow and necessary secrets.

But here’s the gotcha: Azure uses Oryx to build your site. Oryx runs in a container without PowerShell, so your .ps1 scripts won’t work.

The solution? Build the site before Oryx runs. Let Oryx simply upload the results.

Here’s a working GitHub Actions workflow (modified from the default Azure setup):

name: Azure Static Web Apps CI/CD

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - main

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true
          lfs: false

      # ADDING THIS STEP!!!
      - name: Install dependencies and build
        run: |
          npm install
          npm run build

      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          # SKIPPING SECRETS :)
          action: "upload"

          # SETTING CORRECT app_location and output_location
          app_location: "dist"
          api_location: ""
          output_location: "dist"

          # DISABLING build, as it was built on the previous step!
          skip_app_build: true
          skip_api_build: true

# SKIPPING OTHER STUFF ....

Now your site builds with PowerShell and everything — and Azure just hosts it. Stable, fast, free.


If you’re looking for a blog platform where you can run scripts, visualize data, and host it for free — Observable Framework + Azure Static Web Apps is the way to go.