Run the site proxy
The site proxy is code your organization deploys in front of one of its sites. It receives every request for the site’s host and:
- serves its own script at
/edgekit/proxy.js; - forwards every other
/edgekit/request to your tenant host; - fetches everything else from your site’s own server, which these pages call the upstream;
- on HTML pages, asks decide for your
paywallfeature on the server and, when the outcome does not allow the page, removes the gated part it finds before the page reaches the browser.
Your organization runs its own copy, one per site. EdgeKit does not deploy it, update it or roll it back. The code it
runs is createProxy in @edgekitcloud/proxy.
What it needs
Section titled “What it needs”- A route for the whole host: every request for the site’s host must reach your copy. A narrower route of your own on the same host takes precedence for its paths, which then skip the proxy, and the proxy cannot detect this.
- Your site’s origin in Allowed origins: the proxy’s decide call carries the page’s origin, and browser requests
through
/edgekit/carry theirs. Set it on Delivery › Delivery › Sites, then deploy. See Allow your site’s origins. - An analytics route of your own: the proxy forwards
/edgekit/a/tto your tenant host with the path unchanged, but the tenant host answers analytics only at/a/t. The event gets200with the bodyedgekit.cloud alive, so the tracker counts it as delivered, and it is lost. Forward/edgekit/a/*yourself, in front of the proxy, as in Route the paths. Or set the tracker’scollectorOriginto your tenant host and itspathPrefixto an empty string, as in Call the tenant host directly. - A deployed feature named
paywallon the site. It is the only feature the proxy asks for. If the deployment does not carry it, decide lists it as unknown and the proxy treats every page it decides as gated. - The settings below, as environment variables of your copy.
Settings
Section titled “Settings”Every setting starts with EDGEKIT_. If a required setting is missing or invalid, the proxy still serves its script
and passes every other request to your site unchanged. An invalid optional setting takes its default. Either way the proxy logs one warning per problem
when it starts, to your copy’s own log.
| Setting | Required | Default | What it does |
|---|---|---|---|
EDGEKIT_TENANT_HOST |
Yes | Your tenant host, with or without https://. No path |
|
EDGEKIT_SITE_ID |
Yes | The site this copy fronts | |
EDGEKIT_UPSTREAM_URL |
Yes | Your site’s own server. Only its origin is used; a path is ignored with a warning | |
EDGEKIT_BODY_SELECTOR |
No | none | CSS selector for the gated region |
EDGEKIT_PREVIEW_CHILDREN |
No | 2 |
How many child elements of the gated region a truncated page keeps |
EDGEKIT_IGNORE_PATHS |
No | none | Regular expression tested against the path. A matching page is never decided |
EDGEKIT_ACCESS_DEFAULT |
No | metered |
The access fact for a page that does not mark itself free |
EDGEKIT_SECTION_DEFAULT |
No | empty | The section fact for a page that declares none |
EDGEKIT_FACT_OVERRIDES |
No | {} |
JSON object from fact name to source, below |
EDGEKIT_BYPASS_BOT_CATEGORIES |
No | ["Search Engine Crawler"] |
JSON array of verified-bot categories that get the whole page |
EDGEKIT_SIGN_IN_URL |
No | none | Absolute URL the panel links to for sign-in |
EDGEKIT_SUBSCRIBE_URL |
No | none | Absolute URL the panel links to for subscribing |
EDGEKIT_CLIENT_IP_KEY |
No | none | Secret. Signs each user’s IP address for EdgeKit’s rate limits, below |
A JSON setting may be a JSON string or a native object or array.
Which requests it changes
Section titled “Which requests it changes”Only a GET answered 200 with content-type: text/html, at most 2 MB (counted in characters), goes through the
steps below. Every other
response, including redirects, errors, assets and form posts, passes through byte for byte.
1. It reads facts off the page
Section titled “1. It reads facts off the page”| Fact | Where it comes from |
|---|---|
article_id |
The canonical link, else the article’s mainEntityOfPage or url in the page’s structured data, else the request path. The path only, never the host |
sections |
Every articleSection in the structured data, else the article:section meta tag |
section |
The first of sections, else EDGEKIT_SECTION_DEFAULT |
access |
free when the structured data says isAccessibleForFree is true, else EDGEKIT_ACCESS_DEFAULT |
published_at |
datePublished, else the article:published_time meta tag, as ISO 8601 UTC. Left out when the value found does not parse |
page_url |
The request URL without its query string |
EDGEKIT_FACT_OVERRIDES replaces any of these six. Each value is ld:<path> to read a field of the article’s
structured data, <selector>@<attribute> to read the first matching element’s attribute, or a literal value.
The proxy holds no content-signing secret, so these facts reach decide unsigned, at the unverified trust level. A
canvas that branches on trust level sees unverified.
2. It asks decide
Section titled “2. It asks decide”The proxy posts to /edgekit/v1/decide on your tenant host for the paywall feature, with the facts, the page’s
origin, the user’s Cookie, User-Agent, Accept-Language and Referer headers, and the country and IP headers
your edge sets. A gift link in the page URL’s
_gt parameter is sent too. The call has a 2-second deadline.
It skips decide when the request’s verified-bot category is in EDGEKIT_BYPASS_BOT_CATEGORIES, or when the path
matches EDGEKIT_IGNORE_PATHS.
3. It picks a mode
Section titled “3. It picks a mode”| Mode | When | The page |
|---|---|---|
bot |
A verified bot in a bypassed category | Whole, decide not called, your caching headers kept |
allow |
Decide allowed the page, or the path is ignored | Whole |
truncated |
A gated region was found and the outcome does not allow | The first EDGEKIT_PREVIEW_CHILDREN child elements of the region are kept and the rest removed |
overlay |
No gated region was found and the outcome does not allow | Whole. The panel covers it in the browser, and the gated text stays in the page source |
unavailable |
Decide timed out, could not be reached, answered an error status (such as 403 for an origin not in Allowed origins) or answered something other than a decision |
Whole, with no panel |
The gated region is the first of these that applies:
EDGEKIT_BODY_SELECTOR.- A
cssSelectorthe page’s structured data declares for a part that is not free, if it is a plain.classor#idthat occurs in the page. - Elements with the class
edgekit-gated.
With none of them, the mode is overlay. A selector the proxy cannot parse also falls back to overlay, with a
warning. EDGEKIT_BODY_SELECTOR is used as typed: if it matches nothing on the page, nothing is removed and the panel
covers the page as in overlay. In truncated mode, text that sits directly in the region, outside any child element, is not removed.
4. It rewrites the page
Section titled “4. It rewrites the page”In one pass over the page, the proxy:
- adds to
<head>a<script type="application/json" id="edgekit-decision">with the decision, the facts and the mode, and<script type="module" src="/edgekit/proxy.js">; - in
truncatedmode, removes the region’s extra children and leaves an empty<div data-edgekit-panel>where the panel goes; - adds decide’s
Set-Cookieheaders; - when decide answered, sets
Cache-Control: private, no-storeand removesETag,Last-ModifiedandExpires, so no shared cache stores one user’s page for another.
It keeps your security headers, such as Content-Security-Policy, Strict-Transport-Security and
X-Frame-Options. Its script is served from your own origin, so script-src 'self' allows it. The panel is styled by
an inline <style> element, so your style-src, or default-src when there is none, must allow 'unsafe-inline'.
Otherwise the panel draws unstyled, and in overlay mode it no longer covers the page.
The script draws a panel in truncated and overlay modes and never decides again. The panel’s copy is built-in
English text chosen by the outcome’s id. Its link is the url in the outcome’s value when there is one, else
EDGEKIT_SUBSCRIBE_URL or EDGEKIT_SIGN_IN_URL.
When something fails
Section titled “When something fails”| What failed | What the browser gets |
|---|---|
Your tenant host, on an /edgekit/ request |
502 with the text EdgeKit is unreachable |
| Your upstream | 502 with the text Bad gateway |
| Decide, or its 2-second deadline | The whole page, in unavailable mode |
| The rewrite, after decide answered | Your upstream’s page, without decide’s cookies or headers |
Any status your tenant host answers on an /edgekit/ request, such as a 429, passes through unchanged.
Rate limits behind the proxy
Section titled “Rate limits behind the proxy”Decide’s browser path allows 200 requests per 10 seconds per client per site. EdgeKit sees requests from your copy,
not from your users, so without EDGEKIT_CLIENT_IP_KEY all users of the site can share one client’s budget.
With the key, the proxy signs each user’s IP address on every request it sends to your tenant host, and EdgeKit counts each user on their own. EdgeKit issues the key for one site; ask for it. A signature that does not verify, or is more than 60 seconds old, is ignored, never refused: the request is then counted against the address that connected.