feat(v2): add trailingSlash config option (#4908)

* POC: add trailingSlash option

* integrate the preferFoldersOutput option of fork @slorber/static-site-generator-webpack-plugin

* Fix broken links when using trailing slash => using md links is more reliable

* fix TS issue

* minor polish

* fix doc page being sensitive to trailing slashes

* Add tests for applyTrailingSlash

* rename test files

* extract and test applyRouteTrailingSlash

* update snapshot

* add trailing slash config to serve command

* fix getSidebar() => still sensitive to trailing slash setting

* never apply trailing slash to an anchor link

* Add documentation for trailingSlash setting
This commit is contained in:
Sébastien Lorber 2021-06-09 19:59:39 +02:00 committed by GitHub
parent 77264f1eb0
commit df8a900f9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 469 additions and 67 deletions

View file

@ -15,6 +15,7 @@
"start:v2:baseUrl": "yarn workspace docusaurus-2-website start:baseUrl",
"start:v2:bootstrap": "yarn workspace docusaurus-2-website start:bootstrap",
"start:v2:blogOnly": "yarn workspace docusaurus-2-website start:blogOnly",
"start:v2:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace docusaurus-2-website start",
"examples:generate": "node generateExamples",
"build": "yarn build:packages && yarn build:v2",
"build:packages": "lerna run build --no-private",
@ -22,7 +23,9 @@
"build:v2": "yarn workspace docusaurus-2-website build",
"build:v2:baseUrl": "yarn workspace docusaurus-2-website build:baseUrl",
"build:v2:blogOnly": "yarn workspace docusaurus-2-website build:blogOnly",
"build:v2:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace docusaurus-2-website build",
"build:v2:en": "yarn workspace docusaurus-2-website build --locale en",
"clear:v2": "yarn workspace docusaurus-2-website clear",
"serve:v1": "serve website-1.x/build/docusaurus",
"serve:v2": "yarn workspace docusaurus-2-website serve",
"serve:v2:baseUrl": "serve website",

View file

@ -31,15 +31,36 @@ type DocPageContentProps = {
readonly children: ReactNode;
};
function getSidebar({versionMetadata, currentDocRoute}) {
function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
function removeTrailingSlash(str: string): string {
return str.endsWith('/') ? str.slice(0, -1) : str;
}
const {permalinkToSidebar, docsSidebars} = versionMetadata;
// With/without trailingSlash, we should always be able to get the appropriate sidebar
// note: docs plugin permalinks currently never have trailing slashes
// trailingSlash is handled globally at the framework level, not plugin level
const sidebarName =
permalinkToSidebar[currentDocRoute.path] ||
permalinkToSidebar[addTrailingSlash(currentDocRoute.path)] ||
permalinkToSidebar[removeTrailingSlash(currentDocRoute.path)];
const sidebar = docsSidebars[sidebarName];
return {sidebar, sidebarName};
}
function DocPageContent({
currentDocRoute,
versionMetadata,
children,
}: DocPageContentProps): JSX.Element {
const {siteConfig, isClient} = useDocusaurusContext();
const {pluginId, permalinkToSidebar, docsSidebars, version} = versionMetadata;
const sidebarName = permalinkToSidebar[currentDocRoute.path];
const sidebar = docsSidebars[sidebarName];
const {pluginId, version} = versionMetadata;
const {sidebarName, sidebar} = getSidebar({versionMetadata, currentDocRoute});
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
const [hiddenSidebar, setHiddenSidebar] = useState(false);

View file

@ -30,6 +30,8 @@ export interface DocusaurusConfig {
tagline?: string;
title: string;
url: string;
// trailingSlash undefined = legacy retrocompatible behavior => /file => /file/index.html
trailingSlash: boolean | undefined;
i18n: I18nConfig;
onBrokenLinks: ReportingSeverity;
onBrokenMarkdownLinks: ReportingSeverity;

View file

@ -52,7 +52,7 @@
"@docusaurus/types": "2.0.0-beta.0",
"@docusaurus/utils": "2.0.0-beta.0",
"@docusaurus/utils-validation": "2.0.0-beta.0",
"@endiliey/static-site-generator-webpack-plugin": "^4.0.0",
"@slorber/static-site-generator-webpack-plugin": "^4.0.0",
"@svgr/webpack": "^5.5.0",
"autoprefixer": "^10.2.5",
"babel-loader": "^8.2.2",

View file

@ -8,10 +8,12 @@
import React, {useEffect, useRef} from 'react';
import {NavLink, Link as RRLink} from 'react-router-dom';
import useDocusaurusContext from './useDocusaurusContext';
import isInternalUrl from './isInternalUrl';
import ExecutionEnvironment from './ExecutionEnvironment';
import {useLinksCollector} from '../LinksCollector';
import {useBaseUrlUtils} from './useBaseUrl';
import applyTrailingSlash from './applyTrailingSlash';
import type {LinkProps} from '@docusaurus/Link';
import type docusaurus from '../docusaurus';
@ -39,6 +41,9 @@ function Link({
autoAddBaseUrl = true,
...props
}: LinkProps): JSX.Element {
const {
siteConfig: {trailingSlash},
} = useDocusaurusContext();
const {withBaseUrl} = useBaseUrlUtils();
const linksCollector = useLinksCollector();
@ -69,11 +74,15 @@ function Link({
// TODO we should use ReactRouter basename feature instead!
// Automatically apply base url in links that start with /
const targetLink =
let targetLink =
typeof targetLinkWithoutPathnameProtocol !== 'undefined'
? maybeAddBaseUrl(targetLinkWithoutPathnameProtocol)
: undefined;
if (targetLink && isInternal) {
targetLink = applyTrailingSlash(targetLink, trailingSlash);
}
const preloaded = useRef(false);
const LinkComponent = isNavLink ? NavLink : RRLink;

View file

@ -0,0 +1,89 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import applyTrailingSlash from '../applyTrailingSlash';
describe('applyTrailingSlash', () => {
test('should apply to empty', () => {
expect(applyTrailingSlash('', true)).toEqual('/');
expect(applyTrailingSlash('', false)).toEqual('');
expect(applyTrailingSlash('', undefined)).toEqual('');
});
test('should apply to /', () => {
expect(applyTrailingSlash('/', true)).toEqual('/');
expect(applyTrailingSlash('/', false)).toEqual('');
expect(applyTrailingSlash('/', undefined)).toEqual('/');
});
test('should not apply to #anchor links ', () => {
expect(applyTrailingSlash('#', true)).toEqual('#');
expect(applyTrailingSlash('#', false)).toEqual('#');
expect(applyTrailingSlash('#', undefined)).toEqual('#');
expect(applyTrailingSlash('#anchor', true)).toEqual('#anchor');
expect(applyTrailingSlash('#anchor', false)).toEqual('#anchor');
expect(applyTrailingSlash('#anchor', undefined)).toEqual('#anchor');
});
test('should apply to simple paths', () => {
expect(applyTrailingSlash('abc', true)).toEqual('abc/');
expect(applyTrailingSlash('abc', false)).toEqual('abc');
expect(applyTrailingSlash('abc', undefined)).toEqual('abc');
expect(applyTrailingSlash('abc/', true)).toEqual('abc/');
expect(applyTrailingSlash('abc/', false)).toEqual('abc');
expect(applyTrailingSlash('abc/', undefined)).toEqual('abc/');
expect(applyTrailingSlash('/abc', true)).toEqual('/abc/');
expect(applyTrailingSlash('/abc', false)).toEqual('/abc');
expect(applyTrailingSlash('/abc', undefined)).toEqual('/abc');
expect(applyTrailingSlash('/abc/', true)).toEqual('/abc/');
expect(applyTrailingSlash('/abc/', false)).toEqual('/abc');
expect(applyTrailingSlash('/abc/', undefined)).toEqual('/abc/');
});
test('should apply to path with #anchor', () => {
expect(applyTrailingSlash('/abc#anchor', true)).toEqual('/abc/#anchor');
expect(applyTrailingSlash('/abc#anchor', false)).toEqual('/abc#anchor');
expect(applyTrailingSlash('/abc#anchor', undefined)).toEqual('/abc#anchor');
expect(applyTrailingSlash('/abc/#anchor', true)).toEqual('/abc/#anchor');
expect(applyTrailingSlash('/abc/#anchor', false)).toEqual('/abc#anchor');
expect(applyTrailingSlash('/abc/#anchor', undefined)).toEqual(
'/abc/#anchor',
);
});
test('should apply to path with ?search', () => {
expect(applyTrailingSlash('/abc?search', true)).toEqual('/abc/?search');
expect(applyTrailingSlash('/abc?search', false)).toEqual('/abc?search');
expect(applyTrailingSlash('/abc?search', undefined)).toEqual('/abc?search');
expect(applyTrailingSlash('/abc/?search', true)).toEqual('/abc/?search');
expect(applyTrailingSlash('/abc/?search', false)).toEqual('/abc?search');
expect(applyTrailingSlash('/abc/?search', undefined)).toEqual(
'/abc/?search',
);
});
test('should apply to path with ?search#anchor', () => {
expect(applyTrailingSlash('/abc?search#anchor', true)).toEqual(
'/abc/?search#anchor',
);
expect(applyTrailingSlash('/abc?search#anchor', false)).toEqual(
'/abc?search#anchor',
);
expect(applyTrailingSlash('/abc?search#anchor', undefined)).toEqual(
'/abc?search#anchor',
);
expect(applyTrailingSlash('/abc/?search#anchor', true)).toEqual(
'/abc/?search#anchor',
);
expect(applyTrailingSlash('/abc/?search#anchor', false)).toEqual(
'/abc?search#anchor',
);
expect(applyTrailingSlash('/abc/?search#anchor', undefined)).toEqual(
'/abc/?search#anchor',
);
});
});

View file

@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export default function applyTrailingSlash(
path: string,
trailingSlash: boolean | undefined,
): string {
// Never apply trailing slash to an anchor link
if (path.startsWith('#')) {
return path;
}
function addTrailingSlash(str: string): string {
return str.endsWith('/') ? str : `${str}/`;
}
function removeTrailingSlash(str: string): string {
return str.endsWith('/') ? str.slice(0, -1) : str;
}
// undefined = legacy retrocompatible behavior
if (typeof trailingSlash === 'undefined') {
return path;
}
// The trailing slash should be handled before the ?search#hash !
const [pathname] = path.split(/[#?]/);
const newPathname = trailingSlash
? addTrailingSlash(pathname)
: removeTrailingSlash(pathname);
return path.replace(pathname, newPathname);
}

View file

@ -42,7 +42,7 @@ export default async function serve(
}
const {
siteConfig: {baseUrl},
siteConfig: {baseUrl, trailingSlash},
} = await loadSiteConfig({
siteDir,
customConfigFilePath: cliOptions.config,
@ -67,6 +67,7 @@ export default async function serve(
serveHandler(req, res, {
cleanUrls: true,
public: dir,
trailingSlash,
});
});

View file

@ -117,6 +117,7 @@ const ConfigSchema = Joi.object({
favicon: Joi.string().required(),
title: Joi.string().required(),
url: URISchema.required(),
trailingSlash: Joi.boolean(), // No default value! undefined = retrocompatible legacy behavior!
i18n: I18N_CONFIG_SCHEMA,
onBrokenLinks: Joi.string()
.equal('ignore', 'log', 'warn', 'error', 'throw')

View file

@ -0,0 +1,104 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import applyRouteTrailingSlash from '../applyRouteTrailingSlash';
import {RouteConfig} from '@docusaurus/types';
function route(path: string, subRoutes?: string[]): RouteConfig {
const result: RouteConfig = {path, component: 'any'};
if (subRoutes) {
result.routes = subRoutes.map((subRoute) => route(subRoute));
}
return result;
}
describe('applyRouteTrailingSlash', () => {
test('apply to empty', () => {
expect(applyRouteTrailingSlash(route(''), true)).toEqual(route('/'));
expect(applyRouteTrailingSlash(route(''), false)).toEqual(route(''));
expect(applyRouteTrailingSlash(route(''), undefined)).toEqual(route(''));
});
test('apply to /', () => {
expect(applyRouteTrailingSlash(route('/'), true)).toEqual(route('/'));
expect(applyRouteTrailingSlash(route('/'), false)).toEqual(route('/'));
expect(applyRouteTrailingSlash(route('/'), undefined)).toEqual(route('/'));
});
test('apply to /abc', () => {
expect(applyRouteTrailingSlash(route('/abc'), true)).toEqual(
route('/abc/'),
);
expect(applyRouteTrailingSlash(route('/abc'), false)).toEqual(
route('/abc'),
);
expect(applyRouteTrailingSlash(route('/abc'), undefined)).toEqual(
route('/abc'),
);
});
test('apply to /abc/', () => {
expect(applyRouteTrailingSlash(route('/abc/'), true)).toEqual(
route('/abc/'),
);
expect(applyRouteTrailingSlash(route('/abc/'), false)).toEqual(
route('/abc'),
);
expect(applyRouteTrailingSlash(route('/abc/'), undefined)).toEqual(
route('/abc/'),
);
});
test('apply to /abc?search#anchor', () => {
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), true)).toEqual(
route('/abc/?search#anchor'),
);
expect(applyRouteTrailingSlash(route('/abc?search#anchor'), false)).toEqual(
route('/abc?search#anchor'),
);
expect(
applyRouteTrailingSlash(route('/abc?search#anchor'), undefined),
).toEqual(route('/abc?search#anchor'));
});
test('apply to /abc/?search#anchor', () => {
expect(applyRouteTrailingSlash(route('/abc/?search#anchor'), true)).toEqual(
route('/abc/?search#anchor'),
);
expect(
applyRouteTrailingSlash(route('/abc/?search#anchor'), false),
).toEqual(route('/abc?search#anchor'));
expect(
applyRouteTrailingSlash(route('/abc/?search#anchor'), undefined),
).toEqual(route('/abc/?search#anchor'));
});
test('apply to subroutes', () => {
expect(
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), true),
).toEqual(route('/abc/', ['/abc/1/', '/abc/2/']));
expect(
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), false),
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
expect(
applyRouteTrailingSlash(route('/abc', ['/abc/1', '/abc/2']), undefined),
).toEqual(route('/abc', ['/abc/1', '/abc/2']));
});
test('apply for complex case', () => {
expect(
applyRouteTrailingSlash(
route('/abc?search#anchor', ['/abc/1?search', '/abc/2#anchor']),
true,
),
).toEqual(
route('/abc/?search#anchor', ['/abc/1/?search', '/abc/2/#anchor']),
);
});
});

View file

@ -0,0 +1,43 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {RouteConfig} from '@docusaurus/types';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils';
export default function applyRouteTrailingSlash(
route: RouteConfig,
trailingSlash: boolean | undefined,
) {
// Never transform "/" to "" => cause router issues ("" catch everything)
if (route.path === '/') {
return route;
}
function getNewRoutePath() {
// undefined = legacy retrocompatible behavior
if (typeof trailingSlash === 'undefined') {
return route.path;
}
// The trailing slash should be handled before the ?search#hash !
// For routing #anchor is normally not possible, but querystring remains possible
const [pathname] = route.path.split(/[#?]/);
const newPathname = trailingSlash
? addTrailingSlash(pathname)
: removeTrailingSlash(pathname);
return route.path.replace(pathname, newPathname);
}
return {
...route,
path: getNewRoutePath(),
...(route.routes && {
routes: route.routes.map((subroute) =>
applyRouteTrailingSlash(subroute, trailingSlash),
),
}),
};
}

View file

@ -22,6 +22,7 @@ import chalk from 'chalk';
import {DEFAULT_PLUGIN_ID} from '../../constants';
import {chain} from 'lodash';
import {localizePluginTranslationFile} from '../translations/translations';
import applyRouteTrailingSlash from './applyRouteTrailingSlash';
export function sortConfig(routeConfigs: RouteConfig[]): void {
// Sort the route config. This ensures that route with nested
@ -136,8 +137,16 @@ export async function loadPlugins({
const dataDirRoot = path.join(context.generatedFilesDir, plugin.name);
const dataDir = path.join(dataDirRoot, pluginId);
const addRoute: PluginContentLoadedActions['addRoute'] = (config) =>
pluginsRouteConfigs.push(config);
const addRoute: PluginContentLoadedActions['addRoute'] = (
initialRouteConfig,
) => {
// Trailing slash behavior is handled in a generic way for all plugins
const finalRouteConfig = applyRouteTrailingSlash(
initialRouteConfig,
context.siteConfig.trailingSlash,
);
pluginsRouteConfigs.push(finalRouteConfig);
};
const createData: PluginContentLoadedActions['createData'] = async (
name,

View file

@ -10,6 +10,7 @@ Object {
"@docusaurus/Link": "../../client/exports/Link.tsx",
"@docusaurus/Noop": "../../client/exports/Noop.ts",
"@docusaurus/Translate": "../../client/exports/Translate.tsx",
"@docusaurus/applyTrailingSlash": "../../client/exports/applyTrailingSlash.tsx",
"@docusaurus/constants": "../../client/exports/constants.ts",
"@docusaurus/context": "../../client/exports/context.ts",
"@docusaurus/isInternalUrl": "../../client/exports/isInternalUrl.ts",

View file

@ -6,7 +6,6 @@
*/
import path from 'path';
import StaticSiteGeneratorPlugin from '@endiliey/static-site-generator-webpack-plugin';
import {Configuration} from 'webpack';
import merge from 'webpack-merge';
@ -16,6 +15,9 @@ import WaitPlugin from './plugins/WaitPlugin';
import LogPlugin from './plugins/LogPlugin';
import {NODE_MAJOR_VERSION, NODE_MINOR_VERSION} from '../constants';
// Forked for Docusaurus: https://github.com/slorber/static-site-generator-webpack-plugin
import StaticSiteGeneratorPlugin from '@slorber/static-site-generator-webpack-plugin';
export default function createServerConfig({
props,
onLinksCollected = () => {},
@ -31,7 +33,7 @@ export default function createServerConfig({
preBodyTags,
postBodyTags,
ssrTemplate,
siteConfig: {noIndex},
siteConfig: {noIndex, trailingSlash},
} = props;
const config = createBaseConfig(props, true);
@ -75,6 +77,7 @@ export default function createServerConfig({
noIndex,
},
paths: ssgPaths,
preferFoldersOutput: trailingSlash,
}),
// Show compilation progress bar.

View file

@ -81,6 +81,24 @@ module.exports = {
## Optional fields {#optional-fields}
### `trailingSlash` {#trailing-slash}
- Type: `boolean | undefined`
Allow to customize the presence/absence of a trailing slash at the end of URLs/links, and how static HTML files are generated:
- `undefined` (default): keeps URLs untouched, and emit `/docs/myDoc/index.html` for `/docs/myDoc.md`
- `true`: add trailing slashes to URLs/links, and emit `/docs/myDoc/index.html` for `/docs/myDoc.md`
- `false`: remove trailing slashes from URLs/links, and emit `/docs/myDoc.html` for `/docs/myDoc.md`
:::tip
Each static hosting provider serve static files differently (this behavior may even change over time).
Refer to the [deployment guide](../deployment.mdx) and [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) to choose the appropriate setting.
:::
### `i18n` {#i18n}
- Type: `Object`

View file

@ -4,7 +4,7 @@ title: '📦 plugin-google-analytics'
slug: '/api/plugins/@docusaurus/plugin-google-analytics'
---
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag) instead.
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag.md) instead.
## Installation {#installation}

View file

@ -9,32 +9,60 @@ To build the static files of your website for production, run:
npm run build
```
Once it finishes, the static files will be generated within the `build/` directory.
Once it finishes, the static files will be generated within the `build` directory.
You can deploy your site to static site hosting services such as [Vercel](https://vercel.com/), [GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Render](https://render.com/docs/static-sites), and [Surge](https://surge.sh/help/getting-started-with-surge). Docusaurus sites are statically rendered so they work without JavaScript too!
:::note
## Testing Build Local {#testing-build-local}
The only responsibility of Docusaurus is to build your site and emit static files in `build`.
It is important to test build before deploying to a production. Docusaurus includes a [`docusaurus serve`](cli.md#docusaurus-serve) command to test build locally.
It is now up to you to choose how to host those static files.
:::
You can deploy your site to static site hosting services such as [Vercel](https://vercel.com/), [GitHub Pages](https://pages.github.com/), [Netlify](https://www.netlify.com/), [Render](https://render.com/docs/static-sites), [Surge](https://surge.sh/help/getting-started-with-surge)...
A Docusaurus site is statically rendered, and it can generally work without JavaScript!
## Testing your Build Locally {#testing-build-locally}
It is important to test your build locally before deploying to production.
Docusaurus includes a [`docusaurus serve`](cli.md#docusaurus-serve) command for that:
```bash npm2yarn
npm run serve
```
## Self Hosting {#self-hosting}
## Trailing slash configuration {#trailing-slashes}
:::warning
Docusaurus has a [`trailingSlash` config](./api/docusaurus.config.js.md#trailing-slash), to allow customizing URLs/links and emitted filename patterns.
It is not the most performant solution
The default value generally works fine.
Unfortunately, each static hosting provider has a **different behavior**, and deploying the exact same site to various hosts can lead to distinct results.
Depending on your host, it can be useful to change this config.
:::tip
Use [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) to understand better the behavior of your host and configure `trailingSlash` appropriately.
:::
Docusaurus can be self hosted using [`docusaurus serve`](cli.md#docusaurus-serve). Change port using `--port` and `--host` to change host.
## Self-Hosting {#self-hosting}
Docusaurus can be self-hosted using [`docusaurus serve`](cli.md#docusaurus-serve). Change port using `--port` and `--host` to change host.
```bash npm2yarn
npm run serve -- --build --port 80 --host 0.0.0.0
```
:::warning
It is not the best option, compared to a static hosting provider / CDN.
:::
## Deploying to GitHub Pages {#deploying-to-github-pages}
Docusaurus provides an easy way to publish to [GitHub Pages](https://pages.github.com/). Which is hosting that comes for free with every GitHub repository.
@ -54,10 +82,18 @@ First, modify your `docusaurus.config.js` and add the required params:
In case you want to use your custom domain for GitHub Pages, create a `CNAME` file in the `static` directory. Anything within the `static` directory will be copied to the root of the `build` directory for deployment.
When using a custom domain, you should be able to move back from `baseUrl: '/projectName/'` to `baseUrl: '/'`
You may refer to GitHub Pages' documentation [User, Organization, and Project Pages](https://help.github.com/en/articles/user-organization-and-project-pages) for more details.
:::
:::caution
GitHub Pages adds a trailing slash to Docusaurus URLs by default. Adjusting the `trailingSlash` setting can be useful.
:::
Example:
```jsx {3-6} title="docusaurus.config.js"
@ -67,6 +103,7 @@ module.exports = {
baseUrl: '/',
projectName: 'endiliey.github.io',
organizationName: 'endiliey',
trailingSlash: false,
// ...
};
```
@ -337,9 +374,17 @@ If you did not configure these build options, you may still go to "Site settings
Once properly configured with the above options, your site should deploy and automatically redeploy upon merging to your deploy branch, which defaults to `master`.
:::important
:::warning
Make sure to disable Netlify setting `Pretty URLs` to prevent lowercased URLs, unnecessary redirects and 404 errors.
By default, Netlify adds trailing slashes to Docusaurus URLs.
It is recommended to disable the Netlify setting `Post Processing > Asset Optimization > Pretty Urls` to prevent lowercased URLs, unnecessary redirects and 404 errors.
**Be very careful**: the `Disable asset optimization` global checkbox is broken and does not really disable the `Pretty URLs` setting in practice. Please make sure to **uncheck it independently**.
If you want to keep the `Pretty Urls` Netlify setting on, adjust the `trailingSlash` Docusaurus config appropriately.
Refer to [slorber/trailing-slash-guide](https://github.com/slorber/trailing-slash-guide) for more information.
:::

View file

@ -13,16 +13,27 @@ Markdown docs have their own [Markdown frontmatter](../../api/plugins/plugin-con
## Referencing other documents {#referencing-other-documents}
If you want to reference another document file, you could use the name of the document you want to reference. Docusaurus will convert the file path to be the final website path (and remove the `.md`).
If you want to reference another document file, you could use the relative path of the document you want to link to.
For example, if you are in `doc2.md` and you want to reference `doc1.md` and `folder/doc3.md`:
Docusaurus will convert the file path to be the final document url path (and remove the `.md` extension).
For example, if you are in `folder/doc1.md` and you want to reference `folder/doc2.md`, `folder/subfolder/doc3.md` and `otherFolder/doc4.md`:
```md
I am referencing a [document](doc1.md). Reference to another [document in a folder](folder/doc3.md).
I am referencing a [document](doc2.md).
[Relative document](../doc2.md) referencing works as well.
Reference to another [document in a subfolder](subfolder/doc3.md).
[Relative document](../otherFolder/doc4.md) referencing works as well.
```
One benefit of this approach is that the links to external files will still work if you are viewing the file on GitHub.
:::tip
Another benefit, for versioned docs, is that one versioned doc will link to another doc of the exact same version.
It is better to use relative file paths links instead of relative links:
- links will keep working on the GitHub interface
- you can customize the document slugs without having to update all the links
- a versioned doc will link to another doc of the exact same version
- relative links are very likely to break if you update the [`trailingSlash` config](../../api/docusaurus.config.js.md#trailing-slash)
:::

View file

@ -105,7 +105,7 @@ module.exports = {
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
```css title="/src/css/custom.css"
html[data-theme='light'] .DocSearch {

View file

@ -54,6 +54,10 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
baseUrl,
baseUrlIssueBanner: true,
url: 'https://docusaurus.io',
// Dogfood both settings:
// - force trailing slashes for deploy previews
// - avoid trailing slashes in prod
trailingSlash: isDeployPreview,
stylesheets: [
{
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css',
@ -128,30 +132,34 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging;
],
[
'@docusaurus/plugin-client-redirects',
{
fromExtensions: ['html'],
createRedirects: function (path) {
// redirect to /docs from /docs/introduction,
// as introduction has been made the home doc
if (allDocHomesPaths.includes(path)) {
return [`${path}/introduction`];
}
},
redirects: [
{
from: ['/docs/support', '/docs/next/support'],
to: '/community/support',
isDeployPreview
? // Plugin is disabled for deploy preview because we use trailing slashes on deploy previews
// This plugin is sensitive to trailing slashes, and we don't care much about making it work on deploy previews
{}
: {
fromExtensions: ['html'],
createRedirects: function (path) {
// redirect to /docs from /docs/introduction,
// as introduction has been made the home doc
if (allDocHomesPaths.includes(path)) {
return [`${path}/introduction`];
}
},
redirects: [
{
from: ['/docs/support', '/docs/next/support'],
to: '/community/support',
},
{
from: ['/docs/team', '/docs/next/team'],
to: '/community/team',
},
{
from: ['/docs/resources', '/docs/next/resources'],
to: '/community/resources',
},
],
},
{
from: ['/docs/team', '/docs/next/team'],
to: '/community/team',
},
{
from: ['/docs/resources', '/docs/next/resources'],
to: '/community/resources',
},
],
},
],
[
'@docusaurus/plugin-ideal-image',

View file

@ -105,7 +105,7 @@ module.exports = {
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
```css title="/src/css/custom.css"
html[data-theme='light'] .DocSearch {

View file

@ -105,7 +105,7 @@ module.exports = {
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
```css title="/src/css/custom.css"
html[data-theme='light'] .DocSearch {

View file

@ -105,7 +105,7 @@ module.exports = {
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
```css title="/src/css/custom.css"
html[data-theme='light'] .DocSearch {

View file

@ -4,7 +4,7 @@ title: '📦 plugin-google-analytics'
slug: '/api/plugins/@docusaurus/plugin-google-analytics'
---
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag) instead.
The default [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/) plugin. It is a JavaScript library for measuring how users interact with your website **in the production build**. If you are using Google Analytics 4 you might need to consider using [plugin-google-gtag](./plugin-google-gtag.md) instead.
## Installation {#installation}

View file

@ -105,7 +105,7 @@ module.exports = {
By default, DocSearch comes with a fine-tuned theme that was designed for accessibility, making sure that colors and contrasts respect standards.
Still, you can reuse the [Infima CSS variables](styling-layout#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
Still, you can reuse the [Infima CSS variables](styling-layout.md#styling-your-site-with-infima) from Docusaurus to style DocSearch by editing the `/src/css/custom.css` file.
```css title="/src/css/custom.css"
html[data-theme='light'] .DocSearch {

View file

@ -1296,17 +1296,6 @@
resolved "https://registry.yarnpkg.com/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz#dc3803d04e1409cf88efa4bba0f67667807bdf27"
integrity sha512-QxMjt/Gvur/gLxSoCy7VIyGGGrGmDN+VHcXkN3R2ApoWX0EYUE+hMgPHSW/PV6VVebZ1Nd4t2UnGRBDihu16JQ==
"@endiliey/static-site-generator-webpack-plugin@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz#94bfe58fd83aeda355de797fcb5112adaca3a6b1"
integrity sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==
dependencies:
bluebird "^3.7.1"
cheerio "^0.22.0"
eval "^0.1.4"
url "^0.11.0"
webpack-sources "^1.4.3"
"@eslint/eslintrc@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
@ -3058,6 +3047,17 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@slorber/static-site-generator-webpack-plugin@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841"
integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw==
dependencies:
bluebird "^3.7.1"
cheerio "^0.22.0"
eval "^0.1.4"
url "^0.11.0"
webpack-sources "^1.4.3"
"@stylelint/postcss-css-in-js@^0.37.2":
version "0.37.2"
resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2"