Compare commits

...

1 Commits

Author SHA1 Message Date
Nolann Biron 76a94b8316 Lazy-load OpenAPI and ContentKit stylesheets 2026-07-16 20:28:48 +02:00
11 changed files with 117 additions and 52 deletions
@@ -0,0 +1,9 @@
'use client';
import { createLazyStylesheet } from '../createLazyStylesheet';
/**
* Lazy-loads the ContentKit stylesheet so it only downloads on pages that actually render
* an integration block, instead of shipping in every page's CSS chunk.
*/
export default createLazyStylesheet(() => import('./contentkit.css'));
@@ -2,10 +2,10 @@ import { GITBOOK_INTEGRATIONS_CONTENT_HOST, GITBOOK_INTEGRATIONS_HOST } from '@/
import { tcls } from '@/lib/tailwind';
import type { DocumentBlockIntegration, RenderIntegrationUI } from '@gitbook/api';
import { ContentKit, ContentKitOutput } from '@gitbook/react-contentkit';
import React from 'react';
import type { BlockProps } from '../Block';
import './contentkit.css';
import type { GitBookLinker } from '@/lib/links';
import type { BlockProps } from '../Block';
import {
ContentKitWithClientContext,
type WebframeLinkerData,
@@ -15,6 +15,9 @@ import { contentKitServerContext } from './contentkit';
import { fetchSafeIntegrationUI } from './render';
import { renderIntegrationUi } from './server-actions';
// Lazy so the ContentKit CSS is only fetched on pages that render an integration block.
const ContentKitStyles = React.lazy(() => import('./ContentKitStyles'));
export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegration>) {
const { block, context, style } = props;
@@ -102,6 +105,7 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
return (
<div className={tcls(style)}>
<ContentKitStyles />
{useClientContext ? (
<ContentKitWithClientContext
{...contentKitProps}
@@ -5,7 +5,7 @@ import { tcls } from '@/lib/tailwind';
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import { getOpenAPIContext } from './context';
import { OpenAPIStyles, getOpenAPIContext } from './context';
/**
* Render an openapi block or an openapi-operation block.
@@ -14,6 +14,7 @@ export async function OpenAPIOperation(props: BlockProps<AnyOpenAPIOperationsBlo
const { style } = props;
return (
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
<OpenAPIStyles />
<OpenAPIOperationBody {...props} />
</div>
);
@@ -4,7 +4,7 @@ import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import { getOpenAPIContext } from './context';
import { OpenAPIStyles, getOpenAPIContext } from './context';
/**
* Render an openapi-schemas block.
@@ -13,6 +13,7 @@ export async function OpenAPISchemas(props: BlockProps<OpenAPISchemasBlock>) {
const { style } = props;
return (
<div className={tcls('flex w-full', style, 'max-w-full')}>
<OpenAPIStyles />
<OpenAPISchemasBody {...props} />
</div>
);
@@ -0,0 +1,9 @@
'use client';
import { createLazyStylesheet } from '../createLazyStylesheet';
/**
* Lazy-loads the OpenAPI/Scalar stylesheet. Kept out of the static import graph so the
* ~148KB Scalar CSS only downloads on pages that actually render an OpenAPI block.
*/
export default createLazyStylesheet(() => import('./style.css'));
@@ -5,7 +5,7 @@ import { tcls } from '@/lib/tailwind';
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
import type { BlockProps } from '../Block';
import { getOpenAPIContext } from './context';
import { OpenAPIStyles, getOpenAPIContext } from './context';
/**
* Render an openapi block or an openapi-webhook block.
@@ -14,6 +14,7 @@ export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
const { style } = props;
return (
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
<OpenAPIStyles />
<OpenAPIWebhookBody {...props} />
</div>
);
@@ -1,13 +1,13 @@
import type { JSONDocument } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { type OpenAPIContextInput, checkIsValidLocale } from '@gitbook/react-openapi';
import React from 'react';
import type { BlockProps } from '../Block';
import { PlainCodeBlock } from '../CodeBlock';
import { DocumentView } from '../DocumentView';
import { Heading } from '../Heading';
import './style.css';
import { DEFAULT_LOCALE, getSpaceLocale } from '@/intl/server';
import type { GitBookAnyContext } from '@/lib/context';
import { buildSignedProxyUrl } from '@/lib/openapi/proxy-token';
@@ -17,6 +17,12 @@ import type {
OpenAPIWebhookBlock,
} from '@/lib/openapi/types';
/**
* Lazy loader for the OpenAPI/Scalar stylesheet, rendered by each OpenAPI block so the CSS
* is only fetched on pages that use one.
*/
export const OpenAPIStyles = React.lazy(() => import('./OpenAPIStyles'));
/**
* Get the OpenAPI context to render a block.
*/
@@ -116,52 +116,8 @@ button.openapi-mcp {
@apply !mb-0;
}
/* Method Tags */
.openapi-method,
.openapi-statuscode {
@apply m-0 h-5 min-w-9 justify-center rounded-md text-xs straight-corners:rounded-none circular-corners:rounded-lg uppercase font-mono items-center shrink-0 font-semibold px-1.5 py-0.5 text-tint-12/8 leading-tight align-middle inline-flex whitespace-nowrap;
}
.openapi-method-small {}
.openapi-method-medium {
@apply m-0 px-2.5 py-1 h-6 text-[0.813rem];
}
.toclink .openapi-method {
@apply text-[0.625rem] flex items-center justify-center;
}
.openapi-method-get,
.openapi-statuscode-success {
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100;
}
.openapi-method-post,
.openapi-statuscode-redirection {
@apply bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100;
}
.openapi-method-put,
.openapi-statuscode-informational {
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100;
}
.openapi-method-patch {
@apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100;
}
.openapi-method-delete,
.openapi-statuscode-error {
@apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100;
}
.openapi-method-head,
.openapi-method-options,
.openapi-method-trace,
.openapi-method-hook {
@apply bg-tint;
}
/* Method / status-code tag styles moved to `./tags.css`, loaded globally so the sidebar method
* badges are styled even before an OpenAPI block mounts this deferred stylesheet. */
/* URL */
.openapi-url {
@@ -0,0 +1,56 @@
/*
* OpenAPI method / status-code tag styles.
*
* These live outside the deferred `style.css` and are loaded from the global stylesheet because
* the HTTP method badge (`OpenAPIMethodBadge`) renders in the always-present sidebar (table of
* contents) on every page — not only on pages that mount an OpenAPI block. If they stayed in the
* lazily-loaded stylesheet, the sidebar badges would be unstyled until an OpenAPI page pulled in
* the heavy Scalar CSS.
*/
/* Method Tags */
.openapi-method,
.openapi-statuscode {
@apply m-0 h-5 min-w-9 justify-center rounded-md text-xs straight-corners:rounded-none circular-corners:rounded-lg uppercase font-mono items-center shrink-0 font-semibold px-1.5 py-0.5 text-tint-12/8 leading-tight align-middle inline-flex whitespace-nowrap;
}
.openapi-method-small {}
.openapi-method-medium {
@apply m-0 px-2.5 py-1 h-6 text-[0.813rem];
}
.toclink .openapi-method {
@apply text-[0.625rem] flex items-center justify-center;
}
.openapi-method-get,
.openapi-statuscode-success {
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100;
}
.openapi-method-post,
.openapi-statuscode-redirection {
@apply bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100;
}
.openapi-method-put,
.openapi-statuscode-informational {
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100;
}
.openapi-method-patch {
@apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100;
}
.openapi-method-delete,
.openapi-statuscode-error {
@apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100;
}
.openapi-method-head,
.openapi-method-options,
.openapi-method-trace,
.openapi-method-hook {
@apply bg-tint;
}
@@ -0,0 +1,18 @@
'use client';
/**
* Factory for a fire-and-forget loader of a code-split stylesheet: the CSS only downloads on
* pages that render the associated block, instead of shipping in every page's CSS chunk. The
* returned component renders nothing.
*/
export function createLazyStylesheet(load: () => Promise<unknown>) {
let loaded = false;
return function LazyStylesheet() {
// Load during render (not in an effect) so the request starts as early as possible.
if (!loaded && typeof window !== 'undefined') {
loaded = true;
load();
}
return null;
};
}
@@ -4,6 +4,10 @@
@import "./prose.css";
/* OpenAPI method/status-code tags render in the always-present sidebar, so their styles must
ship globally instead of in the deferred OpenAPI stylesheet. */
@import "../DocumentView/OpenAPI/tags.css";
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still