Todas as docs

Início

  1. 01Primeiros passos
  2. 02Instalação
  3. 03Rastreamento

Recursos

  1. 04O painel
  2. 05Metas
  3. 06Links curtos
  4. 07Origens e agentes de IA
  5. 08Relatórios por email
  6. 09Verificação agendada
  7. 10Pergunte à sua IA

Referência

  1. 11Configuração
  2. 12API HTTP

Plataformas

  1. 13Servidor independente
  2. 14PHP
  3. 15WordPress
  4. 16Drupal
  5. 17Craft CMS
  6. 18Python
  7. 19Rails
  8. 20Ruby
  9. 21Go
  10. 22Java
  11. 23.NET
  12. 24Elixir
  13. 25Rust

Confiança

  1. 26Privacidade

Início

Instalação

Monte o Runlight no Next.js, Nuxt, SvelteKit, Astro, Remix, Express, NestJS, Fastify, Koa, Hono, Bun, Deno ou Cloudflare Workers, ou encontre o guia da sua linguagem.

Toda instalação precisa de uma instância (veja Primeiros passos), das rotas dela montadas em /runlight e da tag do script nas suas páginas. Só as rotas mudam de um framework para outro.

rl.routes() retorna um handler Fetch, handler(request), e também a mesma função com o nome de cada método, então qualquer framework construído sobre Request e Response web pode usá-lo diretamente. Mude o caminho com rl.routes({ basePath: "/stats" }), e a tag do script passa a ser /stats/s.js.

Next.js

No App Router, adicione uma rota catch-all.

app/runlight/[[...path]]/route.ts

import { rl } from "@/lib/runlight";

export const { GET, POST, PUT, PATCH, DELETE, OPTIONS } = rl.routes();

Adicione o script ao seu layout raiz.

app/layout.tsx

<head>
  <script defer src="/runlight/s.js"></script>
</head>

O Runlight precisa do runtime Node (ele usa SQLite, Postgres ou MySQL), que é o padrão dos route handlers.

Express e Node puro

@runlight/sdk/node adapta o handler ao módulo http do Node e, com isso, ao Express, Connect e Koa (por meio de ctx.req e ctx.res).

server.ts

import express from "express";
import { toNodeHandler, observer, shortLinks } from "@runlight/sdk/node";
import { rl } from "./runlight.js";

const app = express();
app.use(shortLinks(rl)); // short links at /go and on custom domains
app.use(observer(rl)); // AI agents reading your pages; optional
app.use(toNodeHandler(rl.routes().handler));

Ele pode ir antes ou depois de qualquer body parser, porque o adaptador também lê um corpo que o Express já processou, e deixa sem ler os corpos das requisições do seu próprio app. shortLinks vai antes das suas próprias rotas, para que um domínio de links chegue primeiro ao Runlight.

NestJS

O Nest roda sobre o Express por padrão, então o mesmo middleware vai em main.ts, antes de listen.

src/main.ts

import { NestFactory } from "@nestjs/core";
import { toNodeHandler } from "@runlight/sdk/node";
import { AppModule } from "./app.module";
import { rl } from "./runlight";

const app = await NestFactory.create(AppModule);
app.use(toNodeHandler(rl.routes().handler));
await app.listen(3000);

Adicione app.use(shortLinks(rl)) antes dele para os links curtos.

Fastify

O Fastify lê os corpos das requisições por conta própria, então dê ao Runlight um escopo de plugin próprio, sem body parsers, e entregue a ele a requisição bruta.

ts

import Fastify from "fastify";
import { toNodeHandler } from "@runlight/sdk/node";
import { rl } from "./runlight.js";

const app = Fastify();
const runlight = toNodeHandler(rl.routes().handler);

app.register(async (scope) => {
  scope.removeAllContentTypeParsers();
  scope.addContentTypeParser("*", (_req, _payload, done) => done(null));
  scope.all("/runlight", (req, reply) => { reply.hijack(); return runlight(req.raw, reply.raw); });
  scope.all("/runlight/*", (req, reply) => { reply.hijack(); return runlight(req.raw, reply.raw); });
});

Os links curtos vão num hook onRequest, como mostra Links curtos.

Koa

ts

import Koa from "koa";
import { toNodeHandler } from "@runlight/sdk/node";
import { rl } from "./runlight.js";

const app = new Koa();
const runlight = toNodeHandler(rl.routes().handler);

app.use(async (ctx, next) => {
  if (ctx.path !== "/runlight" && !ctx.path.startsWith("/runlight/")) return next();
  ctx.respond = false;
  await runlight(ctx.req, ctx.res);
});

Para os links curtos, adicione o middleware mostrado em Links curtos antes deste.

Hono

ts

import { Hono } from "hono";
import { rl } from "./runlight";

const app = new Hono();
const runlight = rl.routes().handler;
app.all("/runlight/*", (c) => runlight(c.req.raw));
app.all("/runlight", (c) => runlight(c.req.raw));

Nuxt e Nitro

Coloque a instância em server/utils/runlight.ts (o Nitro importa tudo o que está ali para você) e depois adicione esta rota.

server/routes/runlight/[...path].ts

export default defineEventHandler((event) => rl.routes().handler(toWebRequest(event)));

Adicione o mesmo arquivo como server/routes/runlight.ts para que o próprio /runlight também responda. A tag do script vai em app.head, no nuxt.config.ts.

nuxt.config.ts

export default defineNuxtConfig({
  app: { head: { script: [{ src: "/runlight/s.js", defer: true }] } },
});

SvelteKit

src/routes/runlight/[...path]/+server.ts

import { rl } from "$lib/runlight";

const { handler } = rl.routes();
export const GET = ({ request }) => handler(request);
export const POST = GET;
export const PUT = GET;
export const PATCH = GET;
export const DELETE = GET;
export const OPTIONS = GET;

Astro

O Astro precisa de um adapter para saída no servidor.

src/pages/runlight/[...path].ts

import { rl } from "../../lib/runlight";

export const prerender = false;
export const ALL = ({ request }) => rl.routes().handler(request);

Remix e React Router

app/routes/runlight.$.ts

import { rl } from "~/runlight.server";

const { handler } = rl.routes();
export const loader = ({ request }) => handler(request);
export const action = ({ request }) => handler(request);

Bun

O Bun não consegue carregar o better-sqlite3, então use o SQLite embutido dele por meio de @runlight/sdk/bun.

server.ts

import { runlight } from "@runlight/sdk";
import { bunSqlite } from "@runlight/sdk/bun";

const rl = runlight({ store: bunSqlite({ path: "./data/runlight.db" }), site: { hostnames: ["example.com"] } });
const runlightRoutes = rl.routes().handler;

Bun.serve({
  port: 3000,
  fetch(request) {
    const { pathname } = new URL(request.url);
    if (pathname === "/runlight" || pathname.startsWith("/runlight/")) return runlightRoutes(request);
    return new Response("Your app");
  },
});

O Elysia e outros frameworks para Bun passam o request do mesmo jeito.

Deno

O Deno serve Requests web diretamente. Use libSQL (um arquivo local ou Turso) ou Postgres como store.

main.ts

import { runlight } from "npm:@runlight/sdk";
import { libsql } from "npm:@runlight/sdk/libsql";
import { createClient } from "npm:@libsql/client";

const rl = runlight({ store: libsql({ client: createClient({ url: "file:runlight.db" }) }), site: { hostnames: ["example.com"] } });
const runlightRoutes = rl.routes().handler;

Deno.serve((request) => {
  const { pathname } = new URL(request.url);
  if (pathname === "/runlight" || pathname.startsWith("/runlight/")) return runlightRoutes(request);
  return new Response("Your app");
});

Cloudflare Workers

Workers não têm disco, então guarde os números no D1. A localização vem dos próprios cabeçalhos da Cloudflare, sem configuração.

src/index.ts

import { runlight } from "@runlight/sdk";
import { d1 } from "@runlight/sdk/d1";

let routes: ReturnType<ReturnType<typeof runlight>["routes"]> | undefined;

export default {
  fetch(request: Request, env: { DB: D1Database; RUNLIGHT_TOKEN: string }) {
    // Made once per Worker instance, so the tables are checked when the instance starts.
    routes ??= runlight({ store: d1({ database: env.DB }), site: { hostnames: ["example.com"] } }).routes({ token: env.RUNLIGHT_TOKEN });
    return routes.handler(request);
  },
};

Um Worker guarda os segredos em env, que o Runlight não consegue ler sozinho, então passe o token para routes() como acima. Defina-o com wrangler secret put RUNLIGHT_TOKEN.

Roteie só /runlight/* para este Worker (ou verifique o caminho primeiro, como no exemplo do Bun) e agende a verificação de hora em hora com um Cron Trigger que chame rl.check().

Qualquer outro caso

Se o seu servidor entrega um Request web, passe-o para rl.routes().handler e retorne a Response. Se ele entrega req e res do Node, use toNodeHandler de @runlight/sdk/node.

Outras linguagens

O Runlight tem uma versão para cada uma destas linguagens, com o mesmo painel e as mesmas tabelas. Cada página traz a instalação, as rotas para os frameworks da linguagem, a verificação agendada e o que difere da biblioteca TypeScript.

Linguagem Instalação Roda em
PHP composer require runlight/runlight Laravel, Symfony, PHP puro e um drop-in para um domínio próprio
Python pip install runlight Django, Flask, FastAPI e qualquer app WSGI ou ASGI
Rails bundle add runlight Rails 7.2, 8.0 e 8.1, por meio de uma engine e um gerador
Ruby bundle add runlight sqlite3 Sinatra, Hanami, Roda e qualquer outro app Rack
Go go get runlight.sh/go net/http, chi, Echo e qualquer router que aceite um http.Handler
Java sh.runlight:runlight do Maven Central O servidor do próprio JDK, servlet containers e Spring Boot
.NET dotnet add package Runlight.AspNetCore ASP.NET Core e qualquer outro app .NET
Elixir {:runlight, "~> 0.1"} em mix.exs Phoenix e qualquer app Plug
Rust cargo add runlight runlight-sqlx axum, hyper e qualquer servidor que aceite um tower service

Todas as linguagens, exceto Elixir e Rust, também têm um servidor independente próprio, e a página do servidor independente cobre o de Node.

Atrás de um proxy

O Runlight lê o endereço do visitante na última entrada de X-Forwarded-For, a que o seu proxy adiciona, e depois em X-Real-IP ou CF-Connecting-IP. Isso está certo atrás da Vercel, Netlify, Cloudflare e da maioria dos load balancers. Quando uma requisição passa por dois proxies, como a Cloudflare na frente do nginx, informe o cabeçalho que traz o endereço do próprio visitante, como trustProxy: "cf-connecting-ip". O endereço é usado para o hash diário do visitante e para descobrir a localização, e o limite de requisições o conta em hash. Ele nunca é guardado. Se o seu app fica exposto diretamente, sem proxy, defina trustProxy: false.