Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom fonts #2981

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/little-geese-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"gitbook-v2": patch
"gitbook": patch
---

Add initial support for loading custom fonts
1 change: 1 addition & 0 deletions .github/composite/deploy-cloudflare/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ runs:
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build worker
run: bun run turbo build:v2:cloudflare
shell: bash
Expand Down
2 changes: 2 additions & 0 deletions .github/composite/deploy-vercel/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ runs:
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build Project Artifacts
run: bun run vercel build --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }}
shell: bash
Expand All @@ -74,3 +75,4 @@ runs:
shell: bash
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"

189 changes: 157 additions & 32 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@codemirror/state": "6.4.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"@gitbook/api": "0.99.0"
"@gitbook/api": "https://pkg.pr.new/GitbookIO/integrations/@gitbook/api@759"
},
"private": true,
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions packages/gitbook-v2/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const nextConfig = {
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,

// Next.js envs
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
Expand Down
2 changes: 2 additions & 0 deletions packages/gitbook-v2/src/app/~gitbook/env/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GITBOOK_APP_URL,
GITBOOK_ASSETS_URL,
GITBOOK_DISABLE_TRACKING,
GITBOOK_FONTS_URL,
GITBOOK_ICONS_URL,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_INTEGRATIONS_HOST,
Expand All @@ -25,6 +26,7 @@ export async function GET(_req: NextRequest) {
GITBOOK_API_URL,
GITBOOK_API_PUBLIC_URL,
GITBOOK_ASSETS_URL,
GITBOOK_FONTS_URL,
GITBOOK_ICONS_URL,
GITBOOK_USER_AGENT,
GITBOOK_INTEGRATIONS_HOST,
Expand Down
5 changes: 5 additions & 0 deletions packages/gitbook-v2/src/lib/env/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export const GITBOOK_DISABLE_TRACKING = Boolean(
export const GITBOOK_INTEGRATIONS_HOST =
process.env.GITBOOK_INTEGRATIONS_HOST || 'integrations.gitbook.com';

/**
* Hostname for fonts.
*/
export const GITBOOK_FONTS_URL = process.env.GITBOOK_FONTS_URL || 'https://fonts.gitbook.com';

/**
* Endpoint to use for resizing images.
* It should be a Cloudflare domain with image resizing enabled.
Expand Down
11 changes: 6 additions & 5 deletions packages/gitbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
"@upstash/redis": "^1.27.1",
"ai": "^4.1.46",
"ajv": "^8.12.0",
"assert-never": "^1.2.1",
"bun-types": "^1.1.20",
"classnames": "^2.5.1",
"framer-motion": "^10.16.14",
"js-cookie": "^3.0.5",
"jsontoxml": "^1.0.1",
"jwt-decode": "^4.0.0",
"katex": "^0.16.9",
"mathjax": "^3.2.2",
"mdast-util-to-markdown": "^2.1.2",
Expand All @@ -62,14 +64,11 @@
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
"ai": "^4.1.46",
"jwt-decode": "^4.0.0"
"usehooks-ts": "^3.1.0"
},
"devDependencies": {
"@argos-ci/playwright": "^3.10.0",
"@cloudflare/next-on-pages": "1.13.7",
"vercel": "^39.3.0",
"@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.49.1",
"@types/js-cookie": "^3.0.6",
Expand All @@ -89,8 +88,10 @@
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"psi": "^4.1.0",
"stylelint": "^16.15.0",
"tailwindcss": "^3.4.0",
"ts-essentials": "^10.0.1",
"typescript": "^5.5.3"
"typescript": "^5.5.3",
"vercel": "^39.3.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
hexToRgb,
} from '@gitbook/colors';
import { IconStyle, IconsProvider } from '@gitbook/icons';
import * as ReactDOM from 'react-dom';

import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
import { getFontData } from '@/fonts';
import { fontNotoColorEmoji, ibmPlexMono } from '@/fonts/default';
import { getSpaceLanguage } from '@/intl/server';
import { getAssetURL } from '@/lib/assets';
import { tcls } from '@/lib/tailwind';
Expand All @@ -31,7 +33,7 @@ import { ClientContexts } from './ClientContexts';

import '@gitbook/icons/style.css';
import './globals.css';
import { GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@v2/lib/env';
import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@v2/lib/env';

/**
* Layout shared between the content and the PDF renderer.
Expand All @@ -48,6 +50,22 @@ export async function CustomizationRootLayout(props: {
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
const sidebarStyles = getSidebarStyles(customization);
const { infoColor, successColor, warningColor, dangerColor } = getSemanticColors(customization);
const fontData = getFontData(customization.styling.font);

// Preconnect and preload custom fonts if needed
if (fontData.type === 'custom') {
ReactDOM.preconnect(GITBOOK_FONTS_URL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work on the server? Preloading in Nextjs seems to have different recommendations: https://nextjs.org/docs/app/building-your-application/data-fetching/fetching#preloading-data

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. The next preload mechanism appears to be more geared toward preloading next data rather than external resources like fonts. I haven't seen any examples in the wild using this pattern to preload custom fonts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emmerich the link you are referring to is not related to this

The actual documentation is: https://react.dev/reference/react-dom/preconnect

The way it works is that Next will emit automatically an HTML tag, ex where we use it for other static assets

Screenshot 2025-03-21 at 20 53 23

When the browser is parsing the HTML, it'll start connecting to the server even before finding any CSS referencing this font (https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preconnect)

With ReactDOM.preload below, it'll start fetching the actual font file as well.

fontData.preloadSources
.flatMap((face) => face.sources)
.forEach(({ url, format }) => {
ReactDOM.preload(url, {
as: 'font',
crossOrigin: 'anonymous',
fetchPriority: 'high',
type: format ? `font/${format}` : undefined,
});
});
}

return (
<html
Expand All @@ -66,14 +84,18 @@ export async function CustomizationRootLayout(props: {
sidebarStyles.list && `sidebar-list-${sidebarStyles.list}`,
'links' in customization.styling && `links-${customization.styling.links}`,
fontNotoColorEmoji.variable,
fonts[customization.styling.font].variable,
ibmPlexMono.variable
ibmPlexMono.variable,
fontData.type === 'default' ? fontData.variable : 'font-custom'
)}
>
<head>
{customization.privacyPolicy.url ? (
<link rel="privacy-policy" href={customization.privacyPolicy.url} />
) : null}

{/* Inject custom font @font-face rules */}
{fontData.type === 'custom' ? <style>{fontData.fontFaceRules}</style> : null}

<style
nonce={
//Since I can't get the nonce to work for inline styles, we need to allow unsafe-inline
Expand Down
Loading