Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion e2e/tests/custom-headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ test.describe('custom headers', async () => {

const htmlContent = await page.content();
expect(htmlContent).toContain(
'<meta name="custom-meta" content="custom-meta-content"><meta name="custom-meta-2" content="custom-meta-content-2">',
'<meta name="custom-meta" content="custom-meta-content">',
);
expect(htmlContent).toContain(
'<meta name="custom-meta-2" content="custom-meta-content-2">',
);
});
});
4 changes: 2 additions & 2 deletions e2e/tests/title-suffix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ test('title suffix', async () => {
expect(
(
await readFile(path.join(appDir, 'doc_build/index.html'), 'utf-8')
).includes('<title data-rh="true">Default Title - Index Suffix</title>'),
).includes('<title>Default Title - Index Suffix</title>'),
).toBeTruthy();

expect(
(await readFile(path.join(appDir, 'doc_build/foo.html'), 'utf-8')).includes(
'<title data-rh="true">Foo | Foo Suffix</title>',
'<title>Foo | Foo Suffix</title>',
),
).toBeTruthy();
});
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"reset": "rimraf ./**/node_modules"
},
"dependencies": {
"@dr.pogodin/react-helmet": "2.0.4",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/mdx": "^3.1.0",
"@mdx-js/react": "^3.1.0",
Expand All @@ -61,6 +60,7 @@
"@rspress/runtime": "workspace:*",
"@rspress/shared": "workspace:*",
"@rspress/theme-default": "workspace:*",
"@unhead/react": "^2.0.0",
"enhanced-resolve": "5.18.1",
"github-slugger": "^2.0.0",
"hast-util-from-html": "^2.0.3",
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ export const OUTPUT_DIR = 'doc_build';
export const APP_HTML_MARKER = '<!--<?- DOC_CONTENT ?>-->';
export const HEAD_MARKER = '<!--<?- HEAD ?>-->';
export const META_GENERATOR = '<!--<?- GENERATOR ?>-->';
export const HTML_START_TAG = '<html';
export const BODY_START_TAG = '<body';

export const DEFAULT_TITLE = 'Rspress';

Expand Down
39 changes: 12 additions & 27 deletions packages/core/src/node/ssg/renderPages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { PluginDriver } from '../PluginDriver';

import { pathToFileURL } from 'node:url';
import { HelmetData } from '@dr.pogodin/react-helmet';
import {
type Unhead,
createHead,
transformHtmlTemplate,
} from '@unhead/react/server';

import {
type PageData,
type Route,
Expand All @@ -13,9 +18,7 @@ import { logger } from '@rspress/shared/logger';
import picocolors from 'picocolors';
import {
APP_HTML_MARKER,
BODY_START_TAG,
HEAD_MARKER,
HTML_START_TAG,
META_GENERATOR,
RSPRESS_VERSION,
} from '../constants';
Expand All @@ -26,8 +29,8 @@ import { renderConfigHead, renderFrontmatterHead } from './renderHead';

interface SSRBundleExports {
render: (
url: string,
helmetContext: object,
pagePath: string,
head: Unhead,
) => Promise<{ appHtml: string; pageData: PageData }>;
routes: Route[];
}
Expand Down Expand Up @@ -84,12 +87,12 @@ export async function renderPages(
return !route.routePath.includes(':');
})
.map(async route => {
const helmetContext = new HelmetData({});
const head = createHead();
const { routePath } = route;
let appHtml = '';
if (render) {
try {
({ appHtml } = await render(routePath, helmetContext.context));
({ appHtml } = await render(routePath, head));
} catch (e) {
logger.error(
`Page "${picocolors.yellow(routePath)}" SSG rendering failed.`,
Expand All @@ -99,8 +102,7 @@ export async function renderPages(
}
}

const { helmet } = helmetContext.context;
let html = htmlTemplate
const replacedHtmlTemplate = htmlTemplate
// During ssr, we already have the title in react-helmet
.replace(/<title>(.*?)<\/title>/gi, '')
// Don't use `string` as second param
Expand All @@ -115,27 +117,10 @@ export async function renderPages(
HEAD_MARKER,
[
await renderConfigHead(config, route),
helmet.title.toString(),
helmet.meta.toString(),
helmet.link.toString(),
helmet.style.toString(),
helmet.script.toString(),
await renderFrontmatterHead(route),
].join(''),
);
if (helmet.htmlAttributes) {
html = html.replace(
HTML_START_TAG,
`${HTML_START_TAG} ${helmet.htmlAttributes?.toString()}`,
);
}

if (helmet.bodyAttributes) {
html = html.replace(
BODY_START_TAG,
`${BODY_START_TAG} ${helmet.bodyAttributes?.toString()}`,
);
}
const html = await transformHtmlTemplate(head, replacedHtmlTemplate);

const normalizeHtmlFilePath = (path: string) => {
const normalizedBase = `${normalizeSlash(config?.base || '/')}/`;
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/runtime/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import { DataContext, useLocation } from '@rspress/runtime';
import { Layout } from '@theme';
import React, { useContext, useLayoutEffect } from 'react';
Expand All @@ -11,7 +10,7 @@ enum QueryStatus {
Hide = '0',
}

export function App({ helmetContext }: { helmetContext?: object }) {
export function App() {
const { setData: setPageData, data } = useContext(DataContext);
const { pathname, search } = useLocation();
useLayoutEffect(() => {
Expand Down Expand Up @@ -41,7 +40,7 @@ export function App({ helmetContext }: { helmetContext?: object }) {
query.get(GLOBAL_COMPONENTS_KEY) === QueryStatus.Hide;

return (
<HelmetProvider context={helmetContext}>
<>
<Layout />
{
// Global UI
Expand All @@ -64,6 +63,6 @@ export function App({ helmetContext }: { helmetContext?: object }) {
});
})
}
</HelmetProvider>
</>
);
}
7 changes: 6 additions & 1 deletion packages/core/src/runtime/ClientApp.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { BrowserRouter, DataContext, ThemeContext } from '@rspress/runtime';
import type { PageData } from '@rspress/shared';
import { useThemeState } from '@theme';
import { UnheadProvider, createHead } from '@unhead/react/client';
import { useMemo, useState } from 'react';
import { App } from './App';

const head = createHead();

// eslint-disable-next-line import/no-commonjs

export function ClientApp({
Expand All @@ -20,7 +23,9 @@ export function ClientApp({
value={useMemo(() => ({ data, setData }), [data, setData])}
>
<BrowserRouter>
<App />
<UnheadProvider head={head}>
<App />
</UnheadProvider>
</BrowserRouter>
</DataContext.Provider>
</ThemeContext.Provider>
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/runtime/ssrServerEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { DataContext, ThemeContext } from '@rspress/runtime';
import { StaticRouter } from '@rspress/runtime/server';
import type { PageData } from '@rspress/shared';
import { type Unhead, UnheadProvider } from '@unhead/react/server';
import { renderToString } from 'react-dom/server';

import { App } from './App';
import { initPageData } from './initPageData';

const DEFAULT_THEME = 'light';

export async function render(
pagePath: string,
helmetContext: object,
head: Unhead,
): Promise<{ appHtml: string; pageData: PageData }> {
const initialPageData = await initPageData(pagePath);

const appHtml = renderToString(
<ThemeContext.Provider value={{ theme: DEFAULT_THEME }}>
<DataContext.Provider value={{ data: initialPageData }}>
<StaticRouter location={pagePath}>
<App helmetContext={helmetContext} />
<UnheadProvider value={head}>
<App />
</UnheadProvider>
</StaticRouter>
</DataContext.Provider>
</ThemeContext.Provider>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/// <reference path="../../index.d.ts" />

import type { LinkHTMLAttributes } from 'react';
import { Helmet, usePageData } from 'rspress/runtime';
import { Head, usePageData } from 'rspress/runtime';

export default function FeedsAnnotations() {
const { page } = usePageData();
const feeds = page.feeds || [];

return (
<Helmet>
<Head>
{feeds.map(({ language, url, mime }) => {
const props: LinkHTMLAttributes<HTMLLinkElement> = {
rel: 'alternate',
Expand All @@ -21,6 +21,6 @@ export default function FeedsAnnotations() {
// biome-ignore lint/correctness/useJsxKeyInIterable: no key props
return <link {...props} />;
})}
</Helmet>
</Head>
);
}
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"reset": "rimraf ./**/node_modules"
},
"dependencies": {
"@dr.pogodin/react-helmet": "2.0.4",
"@rspress/shared": "workspace:*",
"@unhead/react": "^2.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^6.29.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ export {
pathnameToRouteService,
normalizeRoutePath,
} from './route';
export { Helmet } from '@dr.pogodin/react-helmet';
export { Head } from '@unhead/react';
export { NoSSR } from './NoSSR';
2 changes: 1 addition & 1 deletion packages/theme-default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
"reset": "rimraf ./**/node_modules"
},
"dependencies": {
"@dr.pogodin/react-helmet": "2.0.4",
"@mdx-js/react": "2.3.0",
"@rspress/runtime": "workspace:*",
"@rspress/shared": "workspace:*",
"@unhead/react": "^2.0.0",
"body-scroll-lock": "4.0.0-beta.0",
"copy-to-clipboard": "^3.3.3",
"flexsearch": "0.7.43",
Expand Down
17 changes: 10 additions & 7 deletions packages/theme-default/src/layout/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'nprogress/nprogress.css';
import '../../styles';
import { Helmet } from '@dr.pogodin/react-helmet';
import { Content, usePageData } from '@rspress/runtime';
import {
HomeLayout as DefaultHomeLayout,
NotFoundLayout as DefaultNotFoundLayout,
Nav,
} from '@theme';
import { useHead } from '@unhead/react';
import { Head } from '@unhead/react';
import type React from 'react';
import type { NavProps } from '../../components/Nav';
import { useSetup } from '../../logic/sideEffects';
Expand Down Expand Up @@ -155,16 +156,18 @@ export function Layout(props: LayoutProps) {
}
};

useHead({
htmlAttrs: {
lang: currentLang || 'en',
},
});

return (
<>
<Helmet
htmlAttributes={{
lang: currentLang || 'en',
}}
>
<Head>
{title ? <title>{title}</title> : null}
{description ? <meta name="description" content={description} /> : null}
</Helmet>
</Head>
{top}

{pageType !== 'blank' && uiSwitch.showNavbar && (
Expand Down
Loading