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