# Jupyter Notebook Viewer: rendering notebooks as a web service

> nbviewer.org is a small Tornado application around nbconvert with pluggable providers, and the repository is what you run when a conference, a company or a classroom needs read-only notebook rendering.

**jupyter/nbviewer** — nbconvert as a web service: Render Jupyter Notebooks as static web pages

- Repository: https://github.com/jupyter/nbviewer
- Website: https://nbviewer.jupyter.org
- Stars: 2,282 · Forks: 574
- Language: Python
- License: NOASSERTION
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/jupyter-nbviewer

## A thin HTTP service around nbconvert

The repository description says what it is in one line: nbconvert as a web service, rendering Jupyter notebooks as static web pages. Everything else follows from that framing.

What the README says about identity is worth quoting in spirit. NBViewer is the web application behind the public Jupyter Notebook Viewer at nbviewer.org, which is hosted by OVHcloud with CDN services from Fastly. So the code you are reading is what serves a URL millions of people have clicked, and the operational documentation is written by people who have had to keep it up under real traffic.

The practical entry point is a container. If you have Docker installed, you can pull and run the current build:

```shell
docker pull jupyter/nbviewer
docker run -p 8080:8080 jupyter/nbviewer
```

The image is built on each push to master, so the pulled tag is the freshest available rather than a curated release. There are no tagged releases in the repository at all, which is the clearest signal about how this project versions itself: follow master, or build from a commit you have pinned yourself.

The README also redirects support questions to the `jupyter/help` tracker and asks for bugs and features on the `jupyter/nbviewer` project itself, which is the usual shape for a project hosted under a larger umbrella rather than under an individual.

## GitHub rate limits are the first thing to configure

The README gives a warning that reads like housekeeping and is really an operational requirement. It says to be fast and friendly to GitHub, you should set `GITHUB_OAUTH_KEY` and `GITHUB_OAUTH_SECRET`:

```shell
docker run -p 8080:8080 -e 'GITHUB_OAUTH_KEY=YOURKEY' \
                          -e 'GITHUB_OAUTH_SECRET=YOURSECRET' \
                          jupyter/nbviewer
```

Alternatively a GitHub personal access token can be passed as `GITHUB_API_TOKEN`. Either approach raises your GitHub API rate limit from the anonymous one. Without it, an instance that renders notebooks from GitHub will start returning errors for users a few dozen requests later, and the symptom looks like the service being broken rather than a missing environment variable.

The same three variables generalize to GitHub Enterprise. Set `GITHUB_API_URL` to your instance's API root, which for Enterprise is prefixed with `http://hostname/api/v3`:

```shell
docker run -p 8080:8080 -e 'GITHUB_OAUTH_KEY=YOURKEY' \
                          -e 'GITHUB_OAUTH_SECRET=YOURSECRET' \
                          -e 'GITHUB_API_URL=https://ghe.example.com/api/v3/' \
                          jupyter/nbviewer
```

With that set, all GitHub API requests go to your Enterprise instance, so internal notebooks render without leaving the network. That single variable is the difference between NBViewer being a public demo and being usable inside an organization, and it is documented in enough detail to actually deploy.

## Providers are the extension point, and two functions cover most cases

The README's section on extending the viewer is the most useful part of the document for anyone adapting it. A provider is a source of notebooks, or a directory of notebooks, or a directory of directories. The project ships five: `url`, `gist`, `github`, `huggingface` and `local`.

The extension API is deliberately small, and the README explains it in two tiers. If you only need to rewrite URLs, or URIs, of another site or namespace, implement `uri_rewrites`. That lets the front page transform an arbitrary string, usually a URI fragment, escape it correctly and turn it into a canonical nbviewer URL. The README points at the Dropbox provider as a simple example of URL rewriting without using a custom API client.

If you need custom logic such as connecting to an API, implement `default_handlers`. The README points at the GitHub provider as the complex example, since that one deals with authentication, pagination and multiple URL shapes. So a new source is either a rewrite function or a handler module, and the existing providers are the reference implementations for both.

There is also an error handling convenience method for intercepting HTTP errors, described as something that saves you re-implementing upstream error handling. Providers needing user authentication are called out as taking more work, which is honest, and the README links to the open issues labelled as provider proposals.

The consequence for a deployer is that adding an internal notebook source, say a company artifact store or a GitLab instance, is a bounded piece of work rather than a fork.

## Running it locally needs system libraries, memcached and an asset build

A local install is more involved than the container, and the README is candid about why. Several binary packages have to be present first, with `libmemcached-dev`, `libcurl4-openssl-dev`, `pandoc`, `libevent-dev` and `libgnutls28-dev` named as the primary ones. Package names vary by system, and the README links to a salt-states repository for distribution-specific details.

```shell
$ cd <path to repo>
$ pip install -r requirements.txt
```

Static assets are a separate chain. They are maintained with bower and less, which need npm installed, and with the `invoke` Python module. The `invoke less` command accepts a `-d` or `--debug` flag that produces a CSS sourcemap. Assets land in `nbviewer/static/components` and the built output in `nbviewer/static/build`.

Running it follows the same shell-prompt style:

```shell
$ cd <path to repo>
$ python -m nbviewer --debug --no-cache --host=127.0.0.1
```

The `--debug --no-cache` combination is the useful one for development: the server relaunches when a Python file changes and nothing is cached, so template edits show up on refresh. In production the README says memcached is used, and a `docker-compose.yml` is provided to bring up nbviewer and memcached together from a local branch. There is also a `Dockerfile`, a `helm-chart/` directory for Kubernetes, and a `statuspage/` directory, which is a hint about how the hosted instance communicates its own health.

## Behind JupyterHub, and a section on securing it

The README documents a base URL mechanism that tells you this was built to sit inside other people's infrastructure. If `JUPYTERHUB_SERVICE_PREFIX` is set, NBViewer always uses that value as its base URL. If it is not set, the `--base-url` flag passed to `python -m nbviewer` is used instead. The precedence is explicit, which is what you want when the same image runs standalone, behind a reverse proxy and as a JupyterHub service.

The table of contents at the top of the README also names a Security section, Securing the Notebook Viewer, which is not a section you should skip. The service fetches URLs supplied by whoever uses it and converts the result, which is the shape of application that attracts people who want to make it fetch internal addresses or consume unbounded resources. Read that section and put the instance behind whatever rate limiting and egress rules your environment already enforces.

The licence deserves a note. The repository's licence field reads NOASSERTION, so there is no licence identifier GitHub can resolve, even though a `LICENSE.txt` exists at the root. That means the actual terms are whatever is written in that file, and you should read it directly rather than assuming a standard licence. The tree also carries `.flake8`, `.pre-commit-config.yaml`, `CONTRIBUTING.md`, `tasks.py` and `package.json`, so there is a defined linting, hook and task path for contributors. The last push to master was 2026-09-04.

## Conclusion

NBViewer is a good fit for a narrow and common problem: rendering notebooks read-only over HTTP for people who do not have Jupyter installed. The provider architecture is the reason to reach for it rather than writing your own, since URL rewriting is a few lines and an API-backed source is a documented module, and the GitHub Enterprise path means an organization can render its own private notebooks on its own network. Two things to know before you deploy it. The rendering pipeline is nbconvert, so anything that fails to convert will fail here, and the service accepts URLs that it fetches, which makes its security section the part of the README you should read before putting it anywhere public. Pull the container, set `GITHUB_API_TOKEN` so you are not rate limited, bind it behind whatever proxy you already run, and use the `helm-chart/` directory if you deploy to Kubernetes.

## FAQ

### What is Jupyter Notebook mainly used for?

Notebooks are documents that interleave prose, code and output, and they are used to show both the reasoning and the result of a computation. That makes them common in data analysis, teaching and technical writing, where a reader benefits from seeing the code that produced a figure rather than only the figure.

### How do I open an IPYNB file?

The hosted service is one answer: prefix the GitHub or gist URL with nbviewer.org and it renders the notebook read-only in a browser. Another is to run it yourself with the container, or to open the file locally with Jupyter. Note that the file is JSON underneath, so a text editor will show you the cells, but not the rendered output.

### How do I run NBViewer on my own network?

Pull `jupyter/nbviewer` and run it with port 8080 published, which the README says is built on every push to master. Set `GITHUB_API_TOKEN` or the OAuth key and secret so GitHub API calls are not rate limited anonymously. For a local build, `docker build -t nbviewer .` followed by `docker run -p 8080:8080 nbviewer` uses your own branch.

### Can NBViewer render private notebooks from GitHub Enterprise?

Yes, by setting `GITHUB_API_URL` to your instance's API root, which is prefixed with `http://hostname/api/v3`, together with an OAuth key and secret or a personal access token. The README states that with this configured all GitHub API requests go to your Enterprise instance, so internal notebooks are viewable without leaving the network.

### What sources can NBViewer fetch notebooks from?

Five providers ship with it: `url`, `gist`, `github`, `huggingface` and `local`. Adding another is a Python module implementing either `uri_rewrites` for simple URL transformation or `default_handlers` when you need to call an API, with the Dropbox and GitHub providers cited as the two reference examples.

## Sources

- [Issues](https://github.com/jupyter/nbviewer/issues)
- [jupyter/nbviewer on GitHub](https://github.com/jupyter/nbviewer)
- [Project website](https://nbviewer.jupyter.org)
- [README](https://github.com/jupyter/nbviewer/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jupyter-nbviewer
