diff --git a/packages/gitbook/src/components/DocumentView/Integration/ContentKitStyles.tsx b/packages/gitbook/src/components/DocumentView/Integration/ContentKitStyles.tsx new file mode 100644 index 000000000..8bc975792 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/Integration/ContentKitStyles.tsx @@ -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')); diff --git a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx index 1443cc2c2..443d4fbae 100644 --- a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx +++ b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx @@ -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) { const { block, context, style } = props; @@ -102,6 +105,7 @@ export async function IntegrationBlock(props: BlockProps + {useClientContext ? ( + ); diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx index c0fd2ce08..e62d5a1b7 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPISchemas.tsx @@ -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) { const { style } = props; return (
+
); diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIStyles.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIStyles.tsx new file mode 100644 index 000000000..231601f7a --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIStyles.tsx @@ -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')); diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx index 00b6b25cc..1d84d05f2 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIWebhook.tsx @@ -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) { const { style } = props; return (
+
); diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx index 110eef2dd..512c9521e 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/context.tsx @@ -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. */ diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index f7caf8370..e062bfa13 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -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 { diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/tags.css b/packages/gitbook/src/components/DocumentView/OpenAPI/tags.css new file mode 100644 index 000000000..216dc5da5 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/tags.css @@ -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; +} diff --git a/packages/gitbook/src/components/DocumentView/createLazyStylesheet.tsx b/packages/gitbook/src/components/DocumentView/createLazyStylesheet.tsx new file mode 100644 index 000000000..f8c282d57 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/createLazyStylesheet.tsx @@ -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) { + 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; + }; +} diff --git a/packages/gitbook/src/components/RootLayout/globals.css b/packages/gitbook/src/components/RootLayout/globals.css index 06a2f9d2c..3d9c8b739 100644 --- a/packages/gitbook/src/components/RootLayout/globals.css +++ b/packages/gitbook/src/components/RootLayout/globals.css @@ -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