# nbsanity - Share Notebooks as Polished Web Pages in Seconds
Hamel Husain
2024-12-13

![](https://nbsanity.com/assets/nbsanity.png)

At fastai, we’ve long believed that Jupyter Notebooks are an excellent
medium for technical writing, combining live code, visualizations, and
narrative text in a single document. However, sharing notebooks in a way
that’s both beautiful and accessible has always been a challenge. While
GitHub’s notebook viewer is functional, it lacks the polish and features
needed for proper technical communication. Today, we’re introducing
[nbsanity](https://nbsanity.com/), a service that transforms any public
GitHub notebook into a polished web page with just a URL change.

## The Challenge

While GitHub’s rendering is functional, it suffers from several
limitations: the rendering can be sluggish and occasionally fails
completely, there’s no way to collapse or hide code cells, and the
presentation can’t be customized. One particularly frustrating issue is
the lack of horizontal scrolling for code cells, and overall, the
reading experience isn’t optimized for consumption.

[Nbviewer](https://nbviewer.org/) solves some of these issues, but
doesn’t allow you to customize the presentation. We’ve previously
addressed some of these challenges with tools like
[fastpages](https://fastpages.fast.ai/) and
[nbdev](https://nbdev.fast.ai/), but these solutions require setup and
maintenance [1]. We realized there was a need for something simpler - a
solution that would allow instant sharing without any overhead.

I’ve been searching for the perfect low-friction system for technical
writing ever since discovering Simon Willison’s elegant [TIL (Today I
Learned)](https://simonwillison.net/2021/May/2/one-year-of-tils/)
approach. With nbsanity, we finally have it.

## What is nbsanity?

[nbsanity](https://nbsanity.com/) is a free service that renders any
public Jupyter notebook from GitHub or Gists as a polished web page.
There’s no setup, no configuration, and no deployment needed.

nbsanity is powered by [Quarto](https://quarto.org/), an open-source
scientific and technical publishing system. Through our extensive work
with various documentation tools, we’ve found Quarto to be the most
ergonomic static site generator available for notebooks. It offers
seamless integration with both Jupyter and VSCode through dedicated
extensions, while providing remarkable flexibility in output formats -
including presentations, books, PDFs and websites.

One of Quarto’s most powerful features is its “directives” system -
simple cell comments that begin with `#|` that allow you to customize
how your content is rendered. These directives are easy to add and do
not clutter your code. Below are examples of Quarto capabilities you get
access to with nbsanity:

- **Cell Visibility Control**: Hide specific cells with
  `#|include: false` while keeping their execution
- **Output Management**: Show just results with `#|echo: false` or raw
  output with `#|output: asis`
- **Error Handling**: Control error messages with `#|error: false` and
  warnings with `#|warning: false`
- **Content Organization**: Create tab panels with `{.panel-tabset}` and
  callouts with `:::{.callout-note}` (this is not a directive, but
  markdown cell syntax that creates tab panels and callouts.).
- **Layout Control**: Apply custom CSS classes and control figure
  layouts with directives like `#| fig-width:` and `#| layout-ncol:`

*Documentation concerning these directives can be found in the [more
resources](#more-resources) section.*

`nbsanity` is focused on doing one thing well: rendering **public**
notebooks beautifully. This means it only works with notebooks hosted on
GitHub or in Gists. Furthermore, you’ll need to use remote URLs for any
images in your notebooks[2]. These constraints let us deliver a service
that’s simple, fast, and completely maintenance-free for users. Think of
nbsanity as the “pastebin for notebooks” - it’s the fastest way to go
from a GitHub notebook to a polished reading experience.

### We added extra love

In addition to Quarto’s rendering process, we’ve added several
quality-of-life improvements. All rendered notebooks have a (1) table of
contents, (2) link to the original GitHub URL, (3) and wrap text in code
cells.

We’ve even made sure that rendered notebooks have fancy social cards,
thanks to Simon Willison’s
[shot-scraper](https://shot-scraper.datasette.io/):

<blockquote class="twitter-tweet">

<p lang="en" dir="ltr">

Testing the social cards for nbsanity
notebooks<a href="https://t.co/5WkGb5dvU6">https://t.co/5WkGb5dvU6</a>
</p>

— Hamel Husain (@HamelHusain)
<a href="https://twitter.com/HamelHusain/status/1867450424003113264?ref_src=twsrc%5Etfw">December
13, 2024</a>
</blockquote>

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

These social cards show the actual contents of your notebook and help
your posts stand out on social media.

## Getting Started

Using nbsanity couldn’t be simpler. You have two options:

### Option 1: URL Modification

Replace `github.com` with `nbsanity.com` in any GitHub notebook URL.
This works for both repositories and gists. For example:

<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">GitHub URL</span>   <a href="https://github.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline">https://</span></a><a href="https://github.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #800000; text-decoration-color: #800000; font-weight: bold; text-decoration: underline; text-decoration: line-through">github.com</span></a><a href="https://github.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline">/fastai/lm-hackers/blob/main/lm-hackers.ipynb</span></a>
&#10;</pre>

<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">nbsanity URL</span> <a href="https://nbsanity.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline">https://</span></a><a href="https://nbsanity.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #008000; text-decoration-color: #008000; font-weight: bold; text-decoration: underline">nbsanity.com</span></a><a href="https://nbsanity.com/fastai/lm-hackers/blob/main/lm-hackers.ipynb" target="_blank"><span style="color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline">/fastai/lm-hackers/blob/main/lm-hackers.ipynb</span></a>
</pre>

*For gists, the URL format is slightly different:
`nbsanity.com/gist/[username]/[gist_id]`. See [these
instructions](https://nbsanity.com/) for more details.*

### Option 2: Bookmarklet

For even faster conversion, drag this bookmarklet to your bookmarks bar:

<p>

<a class="bookmarklet" href="javascript:(function(e){{if((!location.hostname.includes('github.com')||!location.href.endsWith('.ipynb'))&amp;&amp;!location.hostname.includes('gist.github.com')){{alert('Please use this bookmarklet on a GitHub notebook URL (.ipynb file) or a Gist URL');e.preventDefault();return;}}
const newUrl=location.href.replace(location.hostname,location.hostname.includes('gist.github.com')?'nbsanity.com/gist':'nbsanity.com');window.open(newUrl,'_blank');}})(event);" style="display: inline-block; padding: 0.5rem 1rem; background-color: #4a76d4; color: #ffffff !important; border: 1px solid #4a76d4; border-radius: 0.375rem; font-weight: 500; font-size: 0.875rem; text-decoration: none;"><img src="https://nbsanity.com/assets/icon.png" style="height: 1em; margin-right: 0.5rem;">nbsanity</a>
</p>

Clicking on this bookmarklet while viewing a public GitHub notebook will
perform the necessary url substitution for you.

## A Demo

To demonstrate Quarto’s capabilities, let’s examine one of my favorite
features: code-folding.

### Example 1

To collapse a code cell with an expandable summary, I can add the
following directive to the top of a code cell:

``` python
#| code-fold: true
#| code-summary: "Click to see data preprocessing"
```

These rendering instructions are used to create this effect, but are not
rendered and seen by the reader.

<details class="code-fold">
<summary>Click to see data preprocessing</summary>

``` python
import pandas as pd
import numpy as np

# Create sample data
np.random.seed(42)
data = pd.DataFrame({
    'id': range(1000),
    'value': np.random.normal(0, 1, 1000),
    'category': np.random.choice(['A', 'B', 'C'], 1000)
})

# Preprocessing steps
data['value_normalized'] = (data['value'] - data['value'].mean()) / data['value'].std()
data['value_binned'] = pd.qcut(data['value'], q=5, labels=['Q1', 'Q2', 'Q3', 'Q4', 'Q5'])
```

</details>

### Example 2

To specify that you want readers to have the option to collapse code, we
can use the same `code-fold` directive with a different option:

``` python
#| code-fold: show
```

<details open class="code-fold">
<summary>Code</summary>

``` python
import matplotlib.pyplot as plt

plt.figure(figsize=(8, 4))
plt.plot(np.random.randn(100).cumsum())
plt.title('Random Walk')
plt.show()
```

</details>

![](2024-12-13-nbsanity_files/figure-commonmark/cell-6-output-1.png)

## Important Notes

While nbsanity makes notebook sharing effortless, there are a few key
things to keep in mind to use it well. First, nbsanity is a rendering
service only - it displays your notebooks but does not execute them,
even if you have Quarto directives that say otherwise. This avoids
potential security issues.

nbsanity also has a a caching system that preserves the history of your
notebook renders. Each time you render a notebook, you receive a unique
link corresponding to that specific version. If you later update your
notebook and render it again, you’ll get a new link. All previous
versions remain accessible through their original links. Any new
rendering capabilities we introduce will only apply to new renders,
meaning your existing shared notebooks will maintain their original
appearance.

## Next Steps with nbsanity

We built nbsanity because we believe that reducing friction in sharing
knowledge is important. We’ve been refining nbsanity with our community
of over 2,000 students in our [solveit](https://solveit.fast.ai/)
course, where it’s become an integral part of how students share their
work. Their feedback and usage patterns have helped us polish the tool
into something we love using ourselves.

The best way to get started is to try it yourself:

1.  Visit [nbsanity.com](https://nbsanity.com) and drag the bookmarklet
    to your browser’s bookmark bar
2.  Navigate to any public Jupyter notebook on GitHub
3.  Click the bookmarklet to view the notebook with beautiful Quarto
    rendering

Whether you’re writing “Today I Learned” posts, sharing technical
tutorials, or enhancing your project’s documentation, we hope this tool
makes your technical writing journey a little bit easier. The project is
open source and available on
[GitHub](https://github.com/hamelsmu/nbsanity)—we welcome your feedback
and contributions![3]

*P.S. If you share your notebook using nbsanity on social media, please
tag me—I’d love to see your work! You can find me on
[twitter](https://x.com/HamelHusain) and
[linkedin](https://www.linkedin.com/in/hamelhusain/).*

## More resources

Here are links to Quarto docs I find helpful when authoring notebooks:

1.  [cell
    output](https://quarto.org/docs/reference/cells/cells-jupyter.html#cell-output):
    hide, show, and filter cell output and input.
2.  [code-display](https://quarto.org/docs/reference/formats/html.html#code):
    configure how code is displayed, including line-numbers, folding of
    cells, hiding of cells, etc.
3.  [figures](https://quarto.org/docs/reference/cells/cells-jupyter.html#figures):
    configure how figures are shown
4.  [tables](https://quarto.org/docs/reference/cells/cells-jupyter.html#tables):
    configure how tables are shown
5.  [metadata](https://quarto.org/docs/reference/formats/html.html):
    configure the title, subtitle, date, author and more.
6.  [numbering](https://quarto.org/docs/reference/formats/html.html#numbering):
    toggle section numbering.

[1] [JupyterBook](https://jupyterbook.org/) is another project that
allows you to customize the presentation of notebooks. Like fastpages,
nbdev and other static site generators, these projects require a
non-trivial amount of setup and maintenance.

[2] The reason for requiring remote urls is that we do not want to be
rate limited by the GitHub API in fetching related files.

[3] We need to keep the service minimal, so please expect that we will
be discerning about feature requests and PRs.
