From 2001e4dba5bc386bd8a86da2fb0c9774b1db725b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 12 Dec 2023 21:38:22 +0100 Subject: [PATCH] Use absolute URLs for metadata (icon / social graph) (#30) * Use absolute URLs for metadata (icon / social graph) * Disable cache using "next" instead of "cache" * Use cloudflare module from upstash --- src/app/[spaceId]/[[...pathname]]/page.tsx | 18 +++++++++---- src/lib/api.ts | 31 ++++++++++------------ src/lib/cache.ts | 12 ++++++++- src/lib/links.ts | 4 +-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/app/[spaceId]/[[...pathname]]/page.tsx b/src/app/[spaceId]/[[...pathname]]/page.tsx index e94528991..b81727c71 100644 --- a/src/app/[spaceId]/[[...pathname]]/page.tsx +++ b/src/app/[spaceId]/[[...pathname]]/page.tsx @@ -3,7 +3,7 @@ import { notFound, redirect } from 'next/navigation'; import { SpaceContent } from '@/components/SpaceContent'; import { getDocument } from '@/lib/api'; -import { PageHrefContext, baseUrl, pageHref } from '@/lib/links'; +import { PageHrefContext, absoluteHref, baseUrl, pageHref } from '@/lib/links'; import { getPagePath } from '@/lib/pages'; import { PagePathParams, fetchPageData, getPathnameParam } from '../fetch'; @@ -53,25 +53,33 @@ export async function generateMetadata({ params }: { params: PagePathParams }): return { title: `${page.title} | ${space.title}`, - description: page.description, + description: page.description ?? '', generator: 'GitBook', + // We pass `metadataBase` to avoid warnings from Next, but we still use absolute URLs + // as metadataBase doesn't seem to work well on next-on-cloudflare. metadataBase: new URL(baseUrl()), icons: { icon: [ { - url: customIcon?.light ?? '.gitbook/icon?size=small&theme=light', + url: + customIcon?.light ?? + absoluteHref('.gitbook/icon?size=small&theme=light', true), type: 'image/png', media: '(prefers-color-scheme: light)', }, { - url: customIcon?.dark ?? '.gitbook/icon?size=small&theme=dark', + url: + customIcon?.dark ?? + absoluteHref('.gitbook/icon?size=small&theme=dark', true), type: 'image/png', media: '(prefers-color-scheme: dark)', }, ], }, openGraph: { - images: [customization.socialPreview.url ?? `.gitbook/ogimage/${page.id}`], + images: [ + customization.socialPreview.url ?? absoluteHref(`.gitbook/ogimage/${page.id}`), + ], }, // TODO: remove once the development is finished robots: space.visibility === 'public' && 0 ? 'index, follow' : 'noindex, nofollow', diff --git a/src/lib/api.ts b/src/lib/api.ts index 6488f19e6..0976094d0 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -8,7 +8,7 @@ import { } from '@gitbook/api'; import { headers } from 'next/headers'; -import { cache, cacheResponse } from './cache'; +import { cache, cacheResponse, noCacheFetchOptions } from './cache'; export interface ContentPointer { spaceId: string; @@ -72,10 +72,7 @@ export const getPublishedContentByUrl = cache( secure: false, format: 'json', signal: signal, - // Cloudflare doesn't support the `cache` directive before next-on-pages patches the fetch function - // https://github.com/cloudflare/workerd/issues/698 - // cache: 'no-store', - next: { revalidate: 0 }, + ...noCacheFetchOptions, }); return cacheResponse(response); @@ -91,7 +88,7 @@ export const getPublishedContentByUrl = cache( */ export const getSpace = cache('api.getSpace', async (spaceId: string) => { const response = await api().spaces.getSpaceById(spaceId, { - cache: 'no-store', + ...noCacheFetchOptions, }); return cacheResponse(response); }); @@ -103,18 +100,18 @@ export const getRevisionPages = cache('api.getRevisionPages', async (pointer: Co const { data } = await (async () => { if (pointer.revisionId) { return api().spaces.listPagesInRevisionById(pointer.spaceId, pointer.revisionId, { - cache: 'no-store', + ...noCacheFetchOptions, }); } if (pointer.changeRequestId) { return api().spaces.listPagesInChangeRequest(spaceId, pointer.changeRequestId, { - cache: 'no-store', + ...noCacheFetchOptions, }); } return api().spaces.listPages(pointer.spaceId, { - cache: 'no-store', + ...noCacheFetchOptions, }); })(); return { data: data.pages! }; @@ -134,7 +131,7 @@ export const getRevisionFile = cache( pointer.revisionId, fileId, { - cache: 'no-store', + ...noCacheFetchOptions, }, ); } @@ -145,13 +142,13 @@ export const getRevisionFile = cache( pointer.changeRequestId, fileId, { - cache: 'no-store', + ...noCacheFetchOptions, }, ); } return api().spaces.getFileById(pointer.spaceId, fileId, { - cache: 'no-store', + ...noCacheFetchOptions, }); })(); return cacheResponse(response); @@ -170,7 +167,7 @@ export const getRevisionFile = cache( */ export const getCurrentRevision = cache('api.getCurrentRevision', async (spaceId: string) => { const response = await api().spaces.getCurrentRevision(spaceId, { - cache: 'no-store', + ...noCacheFetchOptions, }); return cacheResponse(response); }); @@ -180,7 +177,7 @@ export const getCurrentRevision = cache('api.getCurrentRevision', async (spaceId */ export const getDocument = cache('api.getDocument', async (spaceId: string, documentId: string) => { const response = await api().spaces.getDocumentById(spaceId, documentId, { - cache: 'no-store', + ...noCacheFetchOptions, }); return cacheResponse(response); }); @@ -190,7 +187,7 @@ export const getDocument = cache('api.getDocument', async (spaceId: string, docu */ export const getSpaceCustomization = cache('api.getSpaceCustomization', async (spaceId: string) => { const response = await api().spaces.getSpacePublishingCustomizationById(spaceId, { - cache: 'no-store', + ...noCacheFetchOptions, }); return cacheResponse(response); }); @@ -200,7 +197,7 @@ export const getSpaceCustomization = cache('api.getSpaceCustomization', async (s */ export const getCollection = cache('api.getCollection', async (collectionId: string) => { const response = await api().collections.getCollectionById(collectionId, { - cache: 'no-store', + ...noCacheFetchOptions, }); return cacheResponse(response); }); @@ -215,7 +212,7 @@ export const getCollectionSpaces = cache( collectionId, {}, { - cache: 'no-store', + ...noCacheFetchOptions, }, ); // TODO: do this filtering on the API side diff --git a/src/lib/cache.ts b/src/lib/cache.ts index dd5def9d0..65ff0fdc2 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -1,4 +1,4 @@ -import { Redis } from '@upstash/redis'; +import { Redis } from '@upstash/redis/cloudflare'; import parseCacheControl from 'parse-cache-control'; const cacheNamespace = process.env.UPSTASH_REDIS_NAMESPACE ?? 'gitbook'; @@ -23,6 +23,16 @@ export interface CacheResult { ttl?: number; } +/** + * Options to pass to the `fetch` call to disable the Next data-cache when wrapped in `cache()`. + */ +export const noCacheFetchOptions: Partial = { + // Cloudflare doesn't support the `cache` directive before next-on-pages patches the fetch function + // https://github.com/cloudflare/workerd/issues/698 + // cache: 'no-store', + next: { revalidate: 0 }, +}; + /** * Cache data from an async function. * We don't use the next.js cache because it has a 2MB limit. diff --git a/src/lib/links.ts b/src/lib/links.ts index ad3708fcf..e8511b4e5 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -50,8 +50,8 @@ export function baseUrl(): string { /** * Create an absolute href in the current content. */ -export function absoluteHref(href: string): string { - const base = basePath(); +export function absoluteHref(href: string, withHost: boolean = false): string { + const base = withHost ? baseUrl() : basePath(); return `${base}${href.startsWith('/') ? href.slice(1) : href}`; }