Skip to content

Commit

Permalink
feat: set up IndexNow
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgoff committed Nov 6, 2024
1 parent c6edc1d commit e598e22
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
36 changes: 36 additions & 0 deletions app/api/indexnow/[keyFile]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { NextRequest } from "next/server";

type IndexNowParams = {
keyFile: string;
};

interface IndexNowProps {
params: IndexNowParams;
}

const BING_INDEXNOW_KEY = process.env.BING_INDEXNOW_KEY;

export async function GET(
request: NextRequest,
{ params: { keyFile } }: IndexNowProps
): Promise<Response> {
const [fileName, fileType] = keyFile.split(".");

if (!BING_INDEXNOW_KEY) {
return new Response("No key found", { status: 500 });
}

if (fileType !== "txt") {
return new Response("Invalid file type", { status: 400 });
}

if (fileName !== BING_INDEXNOW_KEY) {
return new Response("Invalid key", { status: 400 });
}

return new Response(BING_INDEXNOW_KEY, {
headers: {
"Content-Type": "text/plain",
},
});
}
51 changes: 50 additions & 1 deletion app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,51 @@ import { NextRequest, NextResponse } from "next/server";
import tags from "@/lib/api/client/tags";
import { fallbackLng, languages } from "@/lib/i18n/settings";

const HOST = process.env.NEXT_PUBLIC_BASE_URL;
const REVALIDATE_SECRET_TOKEN = process.env.CRAFT_REVALIDATE_SECRET_TOKEN;
const CRAFT_HOMEPAGE_URI = "__home__";
const BING_INDEXNOW_KEY = process.env.BING_INDEXNOW_KEY;
const ENV = process.env.CLOUD_ENV;

/**
* Derived from https://www.indexnow.org/documentation#response
*/
const indexNowStatusText: Record<number, string> = {
200: "OK, URL submitted successfully",
202: "Accepted, URL received. IndexNow key validation pending.",
400: "Bad request, Invalid format",
403: "Forbidden, key not valid (e.g. key not found, file found but key not in the file)",
422: "Unprocessable Entity, URL does not belong to the host or the key is not matching the schema in the protocol",
429: "Too Many Requests, potential spam",
};

const indexNow = async (uri: string) => {
if (!HOST || !BING_INDEXNOW_KEY) return;

const urlList = languages.map((locale) => {
const parts: Array<string> = uri === CRAFT_HOMEPAGE_URI ? [] : [uri];
if (locale !== fallbackLng) {
parts.unshift(locale);
}

return `${HOST}/${parts.join("/")}`;
});

const body = {
host: HOST,
key: BING_INDEXNOW_KEY,
keyLocation: `${HOST}/api/indexnow/${BING_INDEXNOW_KEY}.txt`,
urlList,
};

const { status } = await fetch("https://www.bing.com/indexnow", {
body: JSON.stringify(body),
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
});

console.info(`${status}: ${indexNowStatusText[status]}`);
};

export async function GET(request: NextRequest): Promise<NextResponse> {
const uri = request.nextUrl.searchParams.get("uri");
Expand Down Expand Up @@ -33,11 +76,17 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
parts.unshift(locale);
}

revalidatePath(`/${parts.join("/")}`);
const path = `/${parts.join("/")}`;

revalidatePath(path);
});

revalidateTag(tags.globals);

if (ENV === "PROD") {
await indexNow(uri);
}

return NextResponse.json({ revalidated: true, now: Date.now() });
}

Expand Down
38 changes: 38 additions & 0 deletions cypress/e2e/api/indexnow.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const indexNowKey = Cypress.env("BING_INDEXNOW_KEY");
const baseUrl = "/api/indexnow/";
const keyFile = `${indexNowKey}.txt`;
const invalidFileName = `123.txt`;
const invalidFileType = `${indexNowKey}.pdf`;

context("GET /api/indexnow/[keyFile]", () => {
it("rejects requests with incorrect file type", () => {
cy.request({
url: `${baseUrl}${invalidFileType}`,
method: "GET",
failOnStatusCode: false,
}).then(({ status, body }) => {
expect(body).to.eq("Invalid file type");
expect(status).to.eq(400);
});
});
it("rejects requests with incorrect filename", () => {
cy.request({
url: `${baseUrl}${invalidFileName}`,
method: "GET",
failOnStatusCode: false,
}).then(({ status, body }) => {
expect(body).to.eq("Invalid key");
expect(status).to.eq(400);
});
});
it("returns an indexNow key", () => {
cy.request({
url: `${baseUrl}${keyFile}`,
method: "GET",
}).then(({ status, body, headers }) => {
expect(body).to.eq(indexNowKey);
expect(headers["content-type"]).to.eq("text/plain");
expect(status).to.eq(200);
});
});
});

0 comments on commit e598e22

Please sign in to comment.