xref: /expo/docs/next.config.js (revision b0159875)
1import fsExtra from 'fs-extra';
2import { info as logInfo } from 'next/dist/build/output/log.js';
3import { join } from 'path';
4import rehypeSlug from 'rehype-slug';
5import remarkFrontmatter from 'remark-frontmatter';
6import remarkGFM from 'remark-gfm';
7import remarkMDX from 'remark-mdx';
8import remarkMdxDisableExplicitJsx from 'remark-mdx-disable-explicit-jsx';
9import remarkMDXFrontmatter from 'remark-mdx-frontmatter';
10import semver from 'semver';
11
12import remarkCreateStaticProps from './mdx-plugins/remark-create-static-props.js';
13import remarkExportHeadings from './mdx-plugins/remark-export-headings.js';
14import remarkLinkRewrite from './mdx-plugins/remark-link-rewrite.js';
15import createSitemap from './scripts/create-sitemap.js';
16
17const { copySync, removeSync, readJsonSync } = fsExtra;
18
19// note(simek): We cannot use direct JSON import because ESLint do not support `assert { type: 'json' }` syntax yet:
20// * https://github.com/eslint/eslint/discussions/15305
21const { version, betaVersion } = readJsonSync('./package.json');
22const { VERSIONS } = readJsonSync('./public/static/constants/versions.json');
23const navigation = readJsonSync('./public/static/constants/navigation.json');
24
25// Prepare the latest version by copying the actual exact latest version
26const vLatest = join('pages', 'versions', `v${version}/`);
27const latest = join('pages', 'versions', 'latest/');
28removeSync(latest);
29copySync(vLatest, latest);
30logInfo(`Copied latest Expo SDK version from v${version}`);
31
32/** @type {import('next').NextConfig}  */
33export default {
34  trailingSlash: true,
35  experimental: { esmExternals: true },
36  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
37  compiler: { emotion: true },
38  swcMinify: true,
39  webpack: (config, options) => {
40    // Add support for MDX with our custom loader
41    config.module.rules.push({
42      test: /.mdx?$/,
43      use: [
44        options.defaultLoaders.babel,
45        {
46          loader: '@mdx-js/loader',
47          /** @type {import('@mdx-js/loader').Options} */
48          options: {
49            providerImportSource: '@mdx-js/react',
50            remarkPlugins: [
51              remarkMDX,
52              remarkGFM,
53              [remarkMdxDisableExplicitJsx, { whiteList: ['kbd'] }],
54              remarkFrontmatter,
55              [remarkMDXFrontmatter, { name: 'meta' }],
56              remarkExportHeadings,
57              remarkLinkRewrite,
58              [remarkCreateStaticProps, `{ meta: meta || {}, headings: headings || [] }`],
59            ],
60            rehypePlugins: [rehypeSlug],
61          },
62        },
63      ],
64    });
65
66    // Fix inline or browser MDX usage
67    config.resolve.fallback = { fs: false, path: 'path-browserify' };
68
69    return config;
70  },
71
72  // Create a map of all pages to export
73  async exportPathMap(defaultPathMap, { dev, outDir }) {
74    if (dev) {
75      return defaultPathMap;
76    }
77    const pathMap = Object.assign(
78      ...Object.entries(defaultPathMap).map(([pathname, page]) => {
79        if (pathname.match(/unversioned/)) {
80          // Remove unversioned pages from the exported site
81          return {};
82        } else {
83          // Remove newer unreleased versions from the exported side
84          const versionMatch = pathname.match(/\/v(\d\d\.\d\.\d)\//);
85          if (versionMatch?.[1] && semver.gt(versionMatch[1], betaVersion || version, false)) {
86            return {};
87          }
88        }
89
90        return { [pathname]: page };
91      })
92    );
93
94    const sitemapEntries = createSitemap({
95      pathMap,
96      domain: `https://docs.expo.dev`,
97      output: join(outDir, `sitemap.xml`),
98      // Some of the search engines only track the first N items from the sitemap,
99      // this makes sure our starting and general guides are first, and API index last (in order from new to old)
100      pathsPriority: [
101        ...navigation.generalDirectories,
102        ...navigation.easDirectories,
103        ...VERSIONS.map(version => `versions/${version}`),
104      ],
105      // Some of our pages are "hidden" and should not be added to the sitemap
106      pathsHidden: [...navigation.previewDirectories, ...navigation.archiveDirectories],
107    });
108    logInfo(`�� Generated sitemap with ${sitemapEntries.length} entries`);
109
110    return pathMap;
111  },
112  async headers() {
113    const cacheHeaders = [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }];
114    return [{ source: '/_next/static/:static*', headers: cacheHeaders }];
115  },
116};
117