diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 73f741fcdcd3..370c2a345e40 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -41,6 +41,7 @@ export interface DocusaurusConfig { [key: string]: unknown; } )[]; + ssrTemplate?: string; stylesheets?: ( | string | { @@ -106,6 +107,7 @@ export interface LoadContext { siteConfig: DocusaurusConfig; outDir: string; baseUrl: string; + ssrTemplate?: string; } export interface InjectedHtmlTags { diff --git a/packages/docusaurus/src/client/serverEntry.js b/packages/docusaurus/src/client/serverEntry.js index f0a1261db4ee..5b766f8efafa 100644 --- a/packages/docusaurus/src/client/serverEntry.js +++ b/packages/docusaurus/src/client/serverEntry.js @@ -26,19 +26,18 @@ import { createStatefulLinksCollector, ProvideLinksCollector, } from './LinksCollector'; -import ssrTemplate from './templates/ssr.html.template'; // eslint-disable-next-line no-restricted-imports import {memoize} from 'lodash'; -const getCompiledSSRTemplate = memoize(() => { - return eta.compile(ssrTemplate.trim(), { +const getCompiledSSRTemplate = memoize((template) => { + return eta.compile(template.trim(), { rmWhitespace: true, }); }); -function renderSSRTemplate(data) { - const compiled = getCompiledSSRTemplate(); +function renderSSRTemplate(ssrTemplate, data) { + const compiled = getCompiledSSRTemplate(ssrTemplate); return compiled(data, eta.defaultConfig); } @@ -51,6 +50,7 @@ export default async function render(locals) { postBodyTags, onLinksCollected, baseUrl, + ssrTemplate, } = locals; const location = routesLocation[locals.path]; await preload(routes, location); @@ -90,7 +90,7 @@ export default async function render(locals) { const stylesheets = (bundles.css || []).map((b) => b.file); const scripts = (bundles.js || []).map((b) => b.file); - const renderedHtml = renderSSRTemplate({ + const renderedHtml = renderSSRTemplate(ssrTemplate, { appHtml, baseUrl, htmlAttributes: htmlAttributes || '', diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index f4fbe9bdb336..ba9bfe8bcf89 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -81,6 +81,7 @@ const ConfigSchema = Joi.object({ // See https://github.com/facebook/docusaurus/issues/3378 .unknown(), ), + ssrTemplate: Joi.string(), stylesheets: Joi.array().items( Joi.string(), Joi.object({ diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index 785ebb7ff9b7..20a7890ad4ef 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -7,6 +7,7 @@ import {generate} from '@docusaurus/utils'; import path, {join} from 'path'; +import ssrDefaultTemplate from '../client/templates/ssr.html.template'; import { BUILD_DIR_NAME, CONFIG_FILE_NAME, @@ -42,7 +43,7 @@ export function loadContext( const outDir = customOutDir ? path.resolve(customOutDir) : path.resolve(siteDir, BUILD_DIR_NAME); - const {baseUrl} = siteConfig; + const {baseUrl, ssrTemplate} = siteConfig; return { siteDir, @@ -50,6 +51,7 @@ export function loadContext( siteConfig, outDir, baseUrl, + ssrTemplate, }; } @@ -71,7 +73,7 @@ export async function load( ): Promise { // Context. const context: LoadContext = loadContext(siteDir, customOutDir); - const {generatedFilesDir, siteConfig, outDir, baseUrl} = context; + const {generatedFilesDir, siteConfig, outDir, baseUrl, ssrTemplate} = context; // Plugins. const pluginConfigs: PluginConfig[] = loadPluginConfigs(context); @@ -235,6 +237,7 @@ ${Object.keys(registry) headTags, preBodyTags, postBodyTags, + ssrTemplate: ssrTemplate || ssrDefaultTemplate, }; return props; diff --git a/packages/docusaurus/src/webpack/server.ts b/packages/docusaurus/src/webpack/server.ts index 119962acafdf..6021d920f92a 100644 --- a/packages/docusaurus/src/webpack/server.ts +++ b/packages/docusaurus/src/webpack/server.ts @@ -31,6 +31,7 @@ export default function createServerConfig({ headTags, preBodyTags, postBodyTags, + ssrTemplate, } = props; const config = createBaseConfig(props, true, minify); @@ -70,6 +71,7 @@ export default function createServerConfig({ preBodyTags, postBodyTags, onLinksCollected, + ssrTemplate, }, paths: ssgPaths, }), diff --git a/website/docs/api/docusaurus.config.js.md b/website/docs/api/docusaurus.config.js.md index d3031c90063d..8aaa3053124c 100644 --- a/website/docs/api/docusaurus.config.js.md +++ b/website/docs/api/docusaurus.config.js.md @@ -306,6 +306,50 @@ module.exports = { }; ``` +### `ssrTemplate` + +An HTML template that will be used to render your application. This can be used to set custom attributes on the `body` tags, additional `meta` tags, customize the `viewport`, etc. Please note that Docusaurus will rely on the template to be correctly structured in order to function properly, once you do customize it, you will have to make sure that your template is compliant with the requirements from `upstream`. + +- Type: `string` + +Example: + +```js title="docusaurus.config.js" +module.exports = { + ssrTemplate: ` +> + + + + + <%~ it.headTags %> + <% it.metaAttributes.forEach((metaAttribute) => { %> + <%~ metaAttribute %> + <% }); %> + <% it.stylesheets.forEach((stylesheet) => { %> + + <% }); %> + <% it.scripts.forEach((script) => { %> + + <% }); %> + + itemscope="" itemtype="http://schema.org/Organization"> + <%~ it.preBodyTags %> +
+ <%~ it.appHtml %> +
+
+ Custom markup +
+ <% it.scripts.forEach((script) => { %> + + <% }); %> + <%~ it.postBodyTags %> + + +}; +``` + ### `stylesheets` An array of CSS sources to load. The values can be either strings or plain objects of attribute-value maps. The `` tags will be inserted in the HTML ``.