Toute la documentation

Démarrer

  1. 01Pour commencer
  2. 02Installation
  3. 03Suivi

Fonctionnalités

  1. 04Le tableau de bord
  2. 05Objectifs
  3. 06Liens courts
  4. 07Sources et agents IA
  5. 08Rapports par e-mail
  6. 09Vérification planifiée
  7. 10Interrogez votre IA

Référence

  1. 11Configuration
  2. 12API HTTP

Plateformes

  1. 13Serveur autonome
  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

Confiance

  1. 26Confidentialité

Démarrer

Installation

Montez Runlight dans Next.js, Nuxt, SvelteKit, Astro, Remix, Express, NestJS, Fastify, Koa, Hono, Bun, Deno ou Cloudflare Workers, ou trouvez le guide de votre langage.

Chaque installation a besoin d’une instance (voyez Pour commencer), de ses routes montées sous /runlight et de la balise script sur vos pages. Seules les routes changent d’un framework à l’autre.

rl.routes() renvoie un handler Fetch, handler(request), ainsi que la même fonction sous le nom de chaque méthode. Tout framework construit sur les objets web Request et Response peut donc l’utiliser directement. Changez le chemin avec rl.routes({ basePath: "/stats" }), et la balise script devient /stats/s.js.

Next.js

Dans l’App Router, ajoutez une route attrape-tout.

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

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

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

Ajoutez le script à votre layout racine.

app/layout.tsx

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

Runlight a besoin du runtime Node (il utilise SQLite, Postgres ou MySQL), qui est celui par défaut pour les route handlers.

Express et Node seul

@runlight/sdk/node adapte le handler au module http de Node, et donc à Express, Connect et Koa (via ctx.req et 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));

Il peut se placer avant ou après n’importe quel parseur de corps, car l’adaptateur lit aussi un corps qu’Express a déjà analysé, et il ne touche pas aux corps des requêtes propres à votre application. shortLinks se place avant vos propres routes, pour qu’un domaine de liens atteigne Runlight en premier.

NestJS

Nest tourne sur Express par défaut, donc le même middleware va dans main.ts, avant 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);

Ajoutez app.use(shortLinks(rl)) avant pour les liens courts.

Fastify

Fastify lit lui-même le corps des requêtes. Donnez donc à Runlight son propre scope de plugin, sans parseur de corps, et passez-lui la requête brute.

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); });
});

Les liens courts passent par un hook onRequest, comme le montre Liens courts.

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);
});

Pour les liens courts, ajoutez le middleware décrit dans Liens courts avant celui-ci.

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 et Nitro

Placez l’instance dans server/utils/runlight.ts (Nitro importe pour vous tout ce qui s’y trouve), puis ajoutez cette route.

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

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

Ajoutez le même fichier sous server/routes/runlight.ts pour que /runlight lui-même réponde aussi. La balise script va dans app.head, dans 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

Astro a besoin d’un adaptateur pour le rendu côté serveur.

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

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

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

Remix et 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

Bun ne peut pas charger better-sqlite3, alors utilisez son SQLite intégré via @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");
  },
});

Elysia et les autres frameworks Bun transmettent request de la même façon.

Deno

Deno sert directement les Requests web. Utilisez libSQL (un fichier local ou Turso) ou Postgres pour le stockage.

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

Les Workers n’ont pas de disque, alors gardez les chiffres dans D1. La localisation vient des en-têtes de Cloudflare, sans aucune configuration.

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);
  },
};

Un Worker garde ses secrets dans env, que Runlight ne peut pas lire tout seul, alors passez le jeton à routes() comme ci-dessus. Définissez-le avec wrangler secret put RUNLIGHT_TOKEN.

Ne routez que /runlight/* vers ce Worker (ou vérifiez d’abord le chemin, comme dans l’exemple Bun), et planifiez la vérification horaire avec un Cron Trigger qui appelle rl.check().

Tout le reste

Si votre serveur vous donne une Request web, passez-la à rl.routes().handler et renvoyez la Response. S’il vous donne les objets req et res de Node, utilisez toNodeHandler de @runlight/sdk/node.

Autres langages

Runlight est réécrit pour chacun de ces langages, avec le même tableau de bord et les mêmes tables. Chaque page couvre l’installation, les routes pour ses frameworks, la vérification planifiée et ce qui diffère de la bibliothèque TypeScript.

Langage Installation Tourne dans
PHP composer require runlight/runlight Laravel, Symfony, PHP pur, et un drop-in autonome sur son propre domaine
Python pip install runlight Django, Flask, FastAPI et toute application WSGI ou ASGI
Rails bundle add runlight Rails 7.2, 8.0 et 8.1, via un engine et un générateur
Ruby bundle add runlight sqlite3 Sinatra, Hanami, Roda et toute autre application Rack
Go go get runlight.sh/go net/http, chi, Echo et tout routeur qui accepte un http.Handler
Java sh.runlight:runlight depuis Maven Central Le serveur intégré au JDK, les conteneurs de servlets et Spring Boot
.NET dotnet add package Runlight.AspNetCore ASP.NET Core et toute autre application .NET
Elixir {:runlight, "~> 0.1"} dans mix.exs Phoenix et toute application Plug
Rust cargo add runlight runlight-sqlx axum, hyper et tout serveur qui accepte un service tower

Tous les langages sauf Elixir et Rust ont aussi leur propre serveur autonome, et la page Serveur autonome couvre celui de Node.

Derrière un proxy

Runlight lit l’adresse du visiteur dans la dernière entrée de X-Forwarded-For, celle qu’ajoute votre proxy, puis dans X-Real-IP ou CF-Connecting-IP. C’est le bon choix derrière Vercel, Netlify, Cloudflare et la plupart des répartiteurs de charge. Quand une requête traverse deux proxys, comme Cloudflare devant nginx, indiquez l’en-tête qui contient l’adresse du visiteur lui-même, par exemple trustProxy: "cf-connecting-ip". L’adresse sert au hachage quotidien des visiteurs et à la localisation, et la limite de débit la compte sous forme hachée. Elle n’est jamais stockée. Si votre application est exposée directement, sans proxy, définissez trustProxy: false.