Presenting RDF in a human readable form requires retrieving labels for all IRIs.
Ship a big { iri: "label" } table in the frontend.
Why it's bad: stale the moment a vocabulary changes, duplicated in every app you build, no shared source of truth, needs a redeploy to fix a typo - and it only covers the IRIs you thought of in advance.
For every IRI you display, add an OPTIONAL block pulling its rdfs:label /
skos:prefLabel - with language-fallback logic - and return the labels alongside the data.
Why it's bad: the query balloons - every term needs its own OPTIONAL +
COALESCE, language fallback in SPARQL adds additional bloat, it's slow, and you paste the same label
boilerplate into every query.
Run the main query, collect the IRIs it returned, then query the graph again for their labels.
Trade-off: this adds a second round trip to the graph and makes rendering depend on it again.
Materialise a named graph - e.g. one per focus node - that holds all the labels for its IRIs, so you can pull them alongside the data.
Why it's bad: now you maintain a copy. It drifts from the source, bloats storage, needs a sync/materialisation pipeline to stay current - and it's still inside the triplestore, so every read still lands on the KG.
Take approach 3 (a per-IRI lookup) and move it off the triplestore entirely. Labels are
pre-materialised once into blob storage (R2) and fronted by a tiered edge cache. A lookup is a plain
GET /label?iri=…, resolved at the nearest edge location.
Cloudflare caches each label response per data
centre, shared across everyone routed there. The first request for an IRI in a region warms the cache; with a
long TTL, every later user in that region is served the cached copy - the request never reaches R2. Tiered caching
aggregates this across the network, so cold locations pull from a regional tier rather than round-tripping to
storage. Since the same vocabulary IRIs (rdfs:label, schema:name, …) appear in
everyone's data, hit rates on popular terms are very high.
The Workers Free plan currently allows 100,000 requests per day. The Paid plan starts at $5 per month, includes 10 million monthly requests, and charges $0.30 per additional million; CPU charges can also apply. R2 has separate storage and operation pricing, with no Internet egress fee for direct R2 access. A cache hit still counts as a Workers request, but it avoids Worker CPU and an R2 read. Check the Workers and R2 pricing pages for current rates.
A Workers Cache hit is returned before the Worker runs, so it avoids both application execution and an R2 lookup. Cloudflare operates a globally distributed network, but this project does not publish a latency percentile: measure warm and cold behavior from the regions and clients relevant to your application.