Tools and visualizations for QUIC and HTTP/3, built on qlog.
qvis-react is a React + TypeScript + D3 rewrite of qvis by Robin Marx et al. It is meant as a drop-in replacement: the same five tools, layouts, colours, interactions and URL parameters. The difference is the input format. qvis-react reads only the latest qlog drafts, which are in the final phase of standardization:
- draft-ietf-quic-qlog-main-schema-14
- draft-ietf-quic-qlog-quic-events-13
- draft-ietf-quic-qlog-h3-events-13
| Route | Tool |
|---|---|
#/files |
File manager: upload files, load files by URL, load the demo files, download loaded files |
#/sequence |
Sequence diagram: packets and frames between client and server over time |
#/congestion |
Congestion graph: data sent/acked/lost, congestion window, bytes in flight, flow control limits, RTT |
#/multiplexing |
Multiplexing graph: how HTTP/3 streams share the connection, waterfall and byte ranges |
#/packetization |
Packetization diagram: how HTTP/3 frames map onto QUIC frames and packets (and TLS/TCP for tshark input) |
#/stats |
qlog statistics: file and trace info, event histogram, frames, flow control, HTTP/3 settings |
| Input | Notes |
|---|---|
.qlog |
QlogFile: file_schema urn:ietf:params:qlog:file:contained, serialization_format application/qlog+json |
.sqlog |
QlogFileSeq: file_schema urn:ietf:params:qlog:file:sequential, serialization_format application/qlog+json-seq (RFC 7464 JSON text sequences) |
.netlog / .json (Chrome net-export) |
Converted in the browser to latest-draft qlog (quic:*, http3:*) |
tshark JSON (tshark -T json, TCP + TLS + HTTP/2) |
Converted in the browser; the TCP stack uses the private namespaces tcp:, tls: and http2: (main-schema §8) |
.gz |
Decompressed in the browser |
Event names must be the ones defined by quic-events-13 and h3-events-13 (quic:packet_sent,
quic:recovery_metrics_updated, http3:frame_created, ...). qvis-react does not rename or alias pre-final names:
- Files in an old qlog format (
qlog_versiondraft-00 to draft-02, 0.3, 0.4, withoutfile_schema) are rejected with a message. - Files with the new header but pre-final event names (for example
transport:packet_sentorrecovery:metrics_updated, as several implementations still write) load, with a warning listing those names. The visualizations do not interpret those events. - pcap/pcapng captures are not supported, because decrypting them needs tshark on a server. Set
VITE_PCAP_BACKEND_URLto a qvis-server-compatible backend if you have one that emits latest-draft qlog.
Truncated files (a crashed or still-running endpoint) are repaired where possible, as in qvis.
The same URL shapes as qvis work:
https://<host>/#/files?file=<url>, alsohttps://<host>/?file=<url>andhttps://<host>/?file=<url>#/sequence?list=<url>: a JSON list of files (pcap2qlog list format)?file1=<url>&file2=<url>...- Sequence diagram:
focusOnConnection,focusOnEvent,focusOnPN,forceFull - Multiplexing graph:
multimultiplexingshows "Load all connections at once"
The browser fetches the URL directly. If that fails (for example because the server sends no CORS headers), it
retries through the deployment's own proxy, /api/loadfiles?file=<url>: a Vercel Function on Vercel, the same code in
the local server (see Running locally). The proxy only fetches public http(s) URLs and limits
the response size.
Limits that qvis did not have (security review): one ?list= or ?file1..N= link loads at most 100 files, at most 6
at a time, and a compressed file may inflate to at most 512 MiB in the browser.
Option 3 ("Load example .qlog files") and Option 4 (the massive demo file) on #/files load the files in public/examples/.
They were generated with quic-go v0.64.1 patched to write strictly conforming
latest-draft qlog (HTTP/3 included), and validated against the CDDL of the drafts. See
tools/qloggen/ to regenerate them, and scripts/cddl/ for the
validator (node scripts/validate-qlog.mjs <file>).
scripts/latest-to-legacy.mjs converts latest-draft files to draft-02, which the original qvis renders. We use it to
compare both tools on the same connection.
You may not need to: qvis-react runs in the browser, so files you open with "Upload" or drag and drop are read on your computer and never uploaded, on the hosted version as well. Run your own copy to work offline, to pin a version, to load files from your own machine or network by URL, or to avoid depending on a hosted site.
You need Node.js 24 or 26 (the LTS releases; 22.22 or later also works) and git. The same commands work in a terminal on Linux and macOS and in PowerShell or Command Prompt on Windows:
git clone https://github.com/mjoras/qvis-react.git
cd qvis-react
npm ci
npm startThen open http://localhost:8080/. Stop the server with Ctrl+C.
npm start builds the app and starts the local server (server/local-server.ts), which
needs nothing besides Node. It serves dist/ with the same headers as the Vercel deployment (including the
Content-Security-Policy), and runs the same CORS proxy at /api/loadfiles and /loadfiles, so ?file= links behave
as they do on the hosted version.
| Task | Command |
|---|---|
| Use another port | npm start -- --port 9000 |
| Serve the existing build without rebuilding | npm run serve |
| Load files from this computer or your network by URL (below) | npm start -- --allow-private |
| Let other machines connect | npm start -- --host 0.0.0.0 |
| Also answer to a DNS name of this machine | npm start -- --host 0.0.0.0 --allowed-hosts qvis.lab.example |
| All options | npm start -- --help |
After a build, node dist-server/server/local-server.js takes the same options (--port 9000, ...) and works the same
in every shell. On Windows:
- If PowerShell says "running scripts is disabled on this system", type
npm.cmdinstead ofnpm, or use Command Prompt. - The npm of older Node releases, in PowerShell, drops the
--: options with a value then stop with "Unexpected argument", and--allow-privateor--helpare silently ignored (check theproxyline the server prints). Use thenode dist-server/server/local-server.js ...form, or update Node. - If port 8080 is refused with
EACCES(Hyper-V, WSL and Docker Desktop reserve port ranges), use another--port.
By default the server listens on 127.0.0.1 and ::1 only, so only your own computer can reach it. It answers only
requests addressed to an IP address, localhost or a name given with --allowed-hosts (or QVIS_ALLOWED_HOSTS), so a
web page cannot reach it through DNS rebinding. With --host 0.0.0.0, other machines can also use its proxy (for
public addresses only).
Loading files from your own machine or network by URL. The proxy, locally as on Vercel, only fetches public
internet addresses by default: it refuses localhost and private network addresses (and this computer's own
addresses), so it cannot be used to reach other services on your computer or network. To load qlogs from a file server
on your computer or LAN, start qvis-react with --allow-private, and serve the files on this computer only, for
example with Python:
npm start -- --allow-private
# in a second terminal (on Windows: py instead of python3)
python3 -m http.server 8000 --bind 127.0.0.1 --directory ./my-qlogsThen open http://localhost:8080/?file=http://localhost:8000/trace.sqlog. With --allow-private the proxy also
fetches from this computer (127.0.0.0/8, ::1 and its own addresses) and from private networks (10.0.0.0/8,
172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7). It still refuses link-local addresses (169.254.0.0/16,
fe80::/10) and the cloud metadata services at 100.100.100.200, fd00:ec2::254 and fd20:ce::254. It answers only the qvis
app itself (browser fetches from the same origin, so open the app as localhost or 127.0.0.1), and only while the
server listens on this computer alone. Avoid file servers with CORS enabled (Access-Control-Allow-Origin: *) for
private traces: any web site open in your browser could then read them. Or just drag the files onto the page.
Without network access. After npm ci and the first build, everything works offline. To run qvis-react on a
machine without network access, build it elsewhere and copy dist/, dist-server/ and vercel.json, keeping that
layout. There, node dist-server/server/local-server.js is all you need, without npm ci.
Only a static file server. dist/ also works from any static file server, for example
python3 -m http.server 8080 --bind 127.0.0.1 --directory dist (py on Windows) or
npx serve dist -l tcp://127.0.0.1:3000. Uploads, drag and drop and the demo files work. Loading a file by URL then
only works if the server hosting the file sends CORS headers, and the Content-Security-Policy is not sent. Opening
dist/index.html straight from disk (file://) does not work: browsers do not run its module script from a file://
page, so it stays blank.
Docker (optional). Node is the lighter way to run qvis-react, but if you prefer containers, the
Dockerfile runs the same local server:
docker build -t qvis-react .
docker run --rm -p 127.0.0.1:8080:8080 qvis-reactThen open http://localhost:8080/. As with the original qvis image the app is on port 8080, but this image also runs
the CORS proxy. -p 127.0.0.1:8080:8080 keeps the port on your own computer; -p 8080:8080 publishes it to your
network. Server options go after the image name. For example, to reach the container from other machines by a DNS name
of this computer: docker run --rm -p 8080:8080 qvis-react --allowed-hosts qvis.lab.example. --allow-private is not
available in the container, since it listens on all of the container's interfaces.
Requires Node 22.22+, 24.15+ or 26 (the test toolchain's requirement; .nvmrc names the version CI uses).
npm install
npm run dev # http://localhost:5173 (also runs the /api/loadfiles proxy)
npm test # unit tests (Vitest)
npm run e2e # browser smoke tests (Playwright; run `npx playwright install chromium` once)
npm run typecheck
npm run lint
npm run build # static site in dist/, local server in dist-server/
npm start # build, then the local server on http://localhost:8080/ (see "Running locally")
node scripts/smoke-local-server.mjs # after a build: smoke test of the local server (CI runs it on every OS)The design and the parity notes are in docs/dev/: ARCHITECTURE.md describes the code layout, and
parity/*.md describe, per tool, the qvis behaviour that qvis-react reproduces and how each input maps onto the latest
drafts.
The repository deploys to Vercel as is: a static Vite build plus the api/loadfiles function
(see vercel.json, which also maps qvis-server's /loadfiles path onto it). Any static host works for the app itself
(the build uses relative paths, so it also works from a subdirectory); without the function, files on servers without
CORS headers cannot be loaded by URL.
vercel.json also sends a Content-Security-Policy for the app (scripts only from the app's own origin, no plugins,
base-uri and form-action locked to the origin). Do not add inline <script> elements or inline event handlers:
production blocks them, while the dev server (no CSP) still runs them.
The proxy (api/loadfiles.ts) is meant for the app itself, not as a public CORS proxy:
- Browsers may use it only from the app's own origin (
Sec-Fetch-Site: same-origin, or a matchingOrigin). Requests from other sites get 403, and so do navigations and image, script or frame loads (Sec-Fetch-Destother thanempty). Requests carrying the proxy's own User-Agent (nested proxy URLs) get 508, and the Fetch standard's bad ports (SMTP, IRC, SIP, ...) get 400. - The function has no rate limit of its own: scripts outside a browser can still use it as a GET proxy (up to the size
cap and 60 s per request). Add a rate-limit rule for
/api/loadfilesin the Vercel Firewall of a public deployment.
Environment variables (all optional):
| Variable | Effect |
|---|---|
QVIS_PROXY_MAX_BYTES |
response size cap in bytes (default 128 MB, counted after decoding) |
QVIS_PROXY_ALLOW_HOSTS / QVIS_PROXY_DENY_HOSTS |
comma-separated target host names, or .suffix entries |
QVIS_PROXY_ALLOW_ORIGINS |
browser origins besides the app's own that may use the proxy (comma-separated, * = any); only needed when another deployment's VITE_QVIS_PROXY_URL points here |
VITE_QVIS_PROXY_URL (build time) |
proxy the app uses instead of its own /api/loadfiles |
VITE_PCAP_BACKEND_URL (build time) |
qvis-server-compatible backend for pcap/pcapng URLs |
qvis was created by Robin Marx and contributors at Hasselt University (EDM). qvis-react ports its visualizations and keeps its behaviour. MIT licensed, see LICENSE.