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