Self-hosted service
Blockstream/esplora avatar
Blockstream/esplora

Esplora polls Bitcoin on three clocks, and deploys assets before HTML

Explorer for Bitcoin and Liquid

1,271 stars523 forksJavaScriptMIT

At a glance

What is it?
Esplora is Blockstream's block explorer for Bitcoin and Liquid, a reactive single-page app over the esplora-electrs HTTP API. The interesting parts are operational: three shared polling cadences, a pre-rendering server for noscript users, and a deployment ordering requirement for content-hashed assets that will break your rollout if you ignore it.
Who is it for?
Adopt Esplora if you need an explorer frontend for Bitcoin or an Elements-based chain and want the same interface Blockstream runs at blockstream.info. Do not adopt it if you need a rich node or an indexer, since the data layer is esplora-electrs and this repository is only the web interface.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the repository is and is not

Esplora is a block explorer web interface based on the esplora-electrs HTTP API. It is written as a single-page app in a reactive and functional style using RxJS and cycle.js, and it is the code behind Blockstream.info.

That distinction matters when you are choosing a piece. The indexing, the mempool tracking and the chain data all come from electrs, which is a separate project with its own database. Esplora is the browser application plus the server-side rendering around it.

The feature list reflects what an explorer is for. Blocks, transactions and addresses; Segwit and Bech32 support; previous output and spending transaction details so you can follow money forward and backward; quick search by txid, address, block hash or height through a `/<query>` route; an advanced view with script hex and assembly, witness data and outpoints; a mobile-ready responsive design; 17 languages; and noscript support.

For Liquid and other Elements-based chains it adds confidential transaction support, peg-in and peg-out transactions, and multi-asset handling.

The translation work is organised through `lang/` with fetch and update scripts wired to Transifex, and `flavors/` holds the per-network builds.

The authorisation line in the README is worth noting for a project of this size: Pull Requests Welcome.

Three polling cadences, defined in one file

The client polling design is documented in more detail than most projects bother with, and it is the part most likely to affect your own infrastructure.

Browser polling uses three shared cadence tiers defined in `client/src/const.js`.

Fast is 30 seconds, covering the chain tip, pending block templates and recent mempool transactions on the dashboard and recent-transactions page. Standard is 60 seconds, covering the mempool summary, fee estimates and pending peg transactions. Slow is 10 minutes, covering the Bitcoin market chart, the Liquid high-value-assets panel, and a block-list safety poll used to detect same-height reorganizations.

The rules around when those polls run are what make the design defensible. View-scoped polls run only while their view is active and the browser window is focused, and their first tick occurs after a full interval because route entry already requests the initial data. That avoids duplicate requests when a page opens. The chain tip is global and refreshes immediately when the application starts or when a block, transaction or address page is opened.

There are two behaviours that show real production experience. The block list refreshes when the chain tip height changes, with the slow safety poll covering same-height reorganizations, which a height comparison alone would miss. And a newly observed tip resets pending block template polling and delays the next request by 15 seconds so the electrs cache has time to refresh.

One more detail explains an apparently redundant request: the dashboard asks for both `/mempool/recent` and `/mempool` because they serve different UI contracts. The first supplies the recent transaction list, the second supplies aggregate backlog fields such as transaction count, virtual size, total fees and the fee histogram used for congestion analysis.

Content-hashed assets come with a deployment ordering rule

Production builds create content-hashed copies of the app, stylesheets, lazy-loaded scripts and fonts referenced directly by the stylesheets. Original filenames are retained for rollback and for cached HTML, and an `asset-manifest.json` in the output directory maps original relative paths to hashed paths. Setting `HASH_ASSETS=0` disables hashing and uses plain filenames without a manifest.

Both server-side renderers accept an `ASSET_MANIFEST` environment variable pointing at that file, defaulting to `DEST/asset-manifest.json`. A missing manifest falls back to plain filenames, and `STATIC_ROOT` applies to either form.

The deployment requirements that follow are the part people get wrong, and the README is explicit about all four.

New hashed files must be uploaded with `Cache-Control: public, max-age=31536000, immutable` before updating the HTML. Old hashed files must not be deleted immediately after deployment, because cached HTML and open tabs still reference them. And if the server falls back to `index.html` for unknown paths, hashed-asset misses must return a real 404 instead, so a missing file is never cached as HTML.

That last one is the subtle failure. A single-page app needs the server to answer unknown routes with `index.html`, which is `try_files $uri /index.html` in the nginx example at `contrib/nginx.conf.in`. Applied blindly, that rule also turns a missing hashed script into a 200 response containing HTML, and the browser caches the mistake forever.

The noscript path is a second server

Esplora is a single-page app, which means without JavaScript there is nothing to render. The project solves that with a pre-rendering server rather than by abandoning the SPA model.

You start it with `npm run prerender-server` after setting `STATIC_ROOT` to the origin serving CSS, images and fonts:

bash
export STATIC_ROOT=http://localhost:5000/
npm run prerender-server

It serves on port 5001, with the development server on 5000. The output is static HTML suitable for noscript users, generated by rendering the same views server-side.

The dependencies explain how that is done without introducing a framework. `pug` and `pug-cli` handle templates, `snabbdom-to-html` converts a snabbdom virtual DOM to an HTML string, `pofile` reads source maps for the browserify middleware, `uglify-es` minifies, `bundle-collapser` keeps the dev server's memory bounded, and `cssjanus` handles RTL direction flipping. There is also `move-decimal-point`, which is the kind of dependency that only appears when a build script parses version numbers in file names.

Two consequences are worth planning for. The client and the pre-render server are separate npm projects, which is why `postinstall` installs both. And a pre-rendered page is a snapshot, so anything that has to be current on a page without JavaScript is limited to what the server renders at request time.

Configuration, flavours and the toolchain

All configuration options are optional, which is unusual and convenient for trying the project out.

`NODE_ENV` set to production enables JS minification, `BASE_HREF` sets the base href for the user interface and defaults to `/`, `BASE_PREFIX` prepends a path to every flavour's base href so a multi-network site can be served under a sub-directory such as `/preview/` and `/preview/signet/`, with `API_URL` deliberately left unprefixed so a prefixed build still talks to the same backend. `STATIC_ROOT` roots static assets, `API_URL` defaults to `/api`, `CANONICAL_URL` is only required for opensearch and canonical link tags, `NATIVE_ASSET_LABEL` names the network's native asset and defaults to BTC, and `TARGET_BLOCK_INTERVAL_SECONDS` expects 60 for Elements chains and 600 otherwise, used for confirmation ETAs.

That configuration surface exists because `flavors/` holds one build per network. The same code produces the Bitcoin explorer, the Liquid explorer and the testnet variants.

The toolchain is older than the release numbers suggest. package.json says version 0.1.0 while the release tag is `esplora_v3.0.0` from 2026-08-24, so the version field is not the release identity. The build is Babel transpiling to browserify bundles, `patch-package` for dependency patches, and tests run through `node --require @babel/register --test test/*.test.js`, using the Node built-in test runner rather than a separate framework. `npm-shrinkwrap.json` pins the tree.

CI appears in two places, `.travis.yml` and `.gitlab-ci.yml`, alongside `HACKS.md` and a `build.sh`/`run.sh` pair. The last push was on 2026-10-07 and the repository is not archived, with an MIT licence.

Editorial conclusion

Adopt Esplora if you need an explorer frontend for Bitcoin or an Elements-based chain and want the same interface Blockstream runs at blockstream.info. Do not adopt it if you need a rich node or an indexer, since the data layer is esplora-electrs and this repository is only the web interface. Verify first that your electrs API exposes the endpoints the client expects, that you set API_URL and STATIC_ROOT correctly, and that your deploy uploads hashed assets before the new HTML and keeps old hashed files around, which the README states as a hard requirement.

Frequently asked questions

What backend does the Esplora block explorer need?

Esplora is a web interface based on the esplora-electrs HTTP API. Point it at an electrs instance with the API_URL option, which defaults to /api, or fall back to a remote API such as https://blockstream.info/api/ if you have no local server.

How often does the Esplora client poll?

Three shared tiers defined in client/src/const.js: 30 seconds for the chain tip, pending block templates and recent mempool transactions, 60 seconds for the mempool summary, fee estimates and pending peg transactions, and 10 minutes for the market chart, the Liquid high-value-assets panel and a block-list safety poll for same-height reorganizations.

How do I deploy Esplora with content-hashed assets?

Upload the new hashed files with `Cache-Control: public, max-age=31536000, immutable` before updating the HTML, do not delete old hashed files right after deploying, and make sure hashed-asset misses return a real 404 rather than falling back to index.html, since the SPA fallback would otherwise cache an HTML page in place of a script.

Does Esplora work without JavaScript?

There is noscript support through a pre-rendering server. Run `npm run prerender-server` with STATIC_ROOT set to the origin serving CSS, images and fonts, and it serves static HTML replies on port 5001 while the development server stays on 5000.

Official sources

  1. Blockstream/esplora on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/blockstream-esplora.svg)](https://hysenlabs.com/projects/blockstream-esplora)

Was this useful?