@glowrank/next
v0.2.0
Published
Next.js adapter for GlowRank own-site content — a one-line App Router route handler with ISR/tag revalidation, sitemap, and connection verification.
Readme
@glowrank/next
Next.js (App Router) adapter for GlowRank own-site content. One route handler serves your GlowRank pages on your domain — with ISR, tag-based revalidation, a sitemap, and the connection-verification probe built in.
This content is yours — pages render on your origin and canonical URLs point at your domain.
Recommended: render Markdown in your own layout
For full control of the look — and a zero-trust integration where you never
inject our HTML — fetch pages with the underlying client and render each page's
markdown field with your own components. GlowRank owns the words; your
design system owns everything else.
// app/guides/[slug]/page.tsx
import { createClient } from "@glowrank/next";
import Markdown from "react-markdown";
const gr = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });
export default async function Guide({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const page = await gr.getPageBySlug(slug);
if (!page) notFound();
return (
<YourArticleLayout title={page.title}>
<Markdown>{page.markdown ?? ""}</Markdown>
</YourArticleLayout>
);
}markdown is derived server-side from sanitised HTML and stored, so it always
matches the page body. See Trust model.
Fastest path: the one-line route handler
Don't want to wire up rendering yourself? One handler serves complete, server-sanitised HTML documents:
npm install @glowrank/next// app/guides/[[...slug]]/route.ts (use the base path from your GlowRank connection)
import { createGlowRankHandler } from "@glowrank/next";
export const GET = createGlowRankHandler({ siteKey: process.env.GLOWRANK_SITE_KEY! });That's it. The handler now serves, under /guides:
| Path | Response |
| --- | --- |
| /guides/<slug> | Full HTML document for a published GlowRank page |
| /guides/sitemap.xml | Sitemap of all live GlowRank pages |
| /guides/__glowrank-check | Verification probe (echoes your siteKey) |
| anything else | 404 |
Requires next >= 14 (peer dependency) and Node ≥ 18.
ISR & revalidation
Every upstream fetch is made with next: { revalidate, tags: ['glowrank'] }:
Time-based (ISR): pages refresh automatically every
revalidateseconds (default 300). Override per handler:export const GET = createGlowRankHandler({ siteKey: process.env.GLOWRANK_SITE_KEY!, revalidate: 60, // seconds; or `false` to cache until tag revalidation });On-demand: purge all GlowRank content instantly from a Server Action or webhook route:
import { revalidateTag } from "next/cache"; revalidateTag("glowrank"); // exported as GLOWRANK_CACHE_TAG
Responses also carry Cache-Control: public, s-maxage=<revalidate>,
stale-while-revalidate so CDNs in front of your app behave consistently.
Verification (__glowrank-check)
When you click Verify in the GlowRank dashboard, GlowRank requests
https://your-site.com/<basePath>/__glowrank-check and expects your siteKey
echoed back — proving the SDK is mounted at the path configured on your
connection. The handler answers this automatically (no API call involved), so
verification passes as soon as the route above is deployed. If verification
fails, check that the route directory matches your connection's base path
exactly and that GLOWRANK_SITE_KEY is set in the deployed environment.
Merging into your own sitemap
Prefer one sitemap at the site root? Merge GlowRank entries into
app/sitemap.ts instead:
import type { MetadataRoute } from "next";
import { createClient, glowrankSitemapEntries } from "@glowrank/next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const client = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });
const glowrank = await glowrankSitemapEntries(client);
return [{ url: "https://example.com" }, ...glowrank];
}Not on the App Router?
Use @glowrank/content
directly — the framework-agnostic core this adapter is built on (Express,
Fastify, Remix, plain http, …).
Trust model
GlowRank content is model-generated from inputs we don't fully control (your scraped site copy, review text), so we treat it as untrusted and harden it before it reaches you — at write time and again at the API boundary.
markdown(recommended). No markup to trust — render it with your own components. The zero-trust path.html(sanitised). Allowlist sanitisation keeps only semantic content (headings, paragraphs, lists, emphasis, blockquotes, tables,code/pre,hr/br, http/httpsalinks with forcedrel="noopener noreferrer") and stripsscript/style/iframe/object/embed/form, allon*handlers, andjavascript:/data:URLs. This is whatcreateGlowRankHandlerserves.
Both are safe to render; Markdown just gives you a format with no HTML to review.
License
MIT
