xref: /expo/docs/next.config.js (revision 491cd33f)
1import fsExtra from 'fs-extra';
2import NextLog 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);
28NextLog.info(`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  },
46  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
47  compiler: {
48    emotion: true,
49    reactRemoveProperties: true,
50    removeConsole,
51  },
52  poweredByHeader: false,
53  webpack: (config, options) => {
54    // Add support for MDX with our custom loader
55    config.module.rules.push({
56      test: /.mdx?$/,
57      use: [
58        options.defaultLoaders.babel,
59        {
60          loader: '@mdx-js/loader',
61          /** @type {import('@mdx-js/loader').Options} */
62          options: {
63            providerImportSource: '@mdx-js/react',
64            remarkPlugins: [
65              remarkMDX,
66              remarkGFM,
67              [remarkMdxDisableExplicitJsx, { whiteList: ['kbd'] }],
68              remarkFrontmatter,
69              [remarkMDXFrontmatter, { name: 'meta' }],
70              remarkCodeTitle,
71              remarkExportHeadings,
72              remarkLinkRewrite,
73              [remarkCreateStaticProps, `{ meta: meta || {}, headings: headings || [] }`],
74            ],
75            rehypePlugins: [rehypeSlug],
76          },
77        },
78      ],
79    });
80
81    // Fix inline or browser MDX usage
82    config.resolve.fallback = { fs: false, path: 'path-browserify' };
83
84    return config;
85  },
86
87  // Create a map of all pages to export
88  // https://nextjs.org/docs/api-reference/next.config.js/exportPathMap
89  async exportPathMap(defaultPathMap, { dev, outDir }) {
90    if (dev) {
91      return defaultPathMap;
92    }
93    const pathMap = Object.assign(
94      ...Object.entries(defaultPathMap).map(([pathname, page]) => {
95        if (pathname.match(/unversioned/)) {
96          // Remove unversioned pages from the exported site
97          return {};
98        } else {
99          // Remove newer unreleased versions from the exported side
100          const versionMatch = pathname.match(/\/v(\d\d\.\d\.\d)\//);
101          if (versionMatch?.[1] && semver.gt(versionMatch[1], betaVersion || version, false)) {
102            return {};
103          }
104        }
105
106        return { [pathname]: page };
107      })
108    );
109
110    const sitemapEntries = createSitemap({
111      pathMap,
112      domain: `https://docs.expo.dev`,
113      output: join(outDir, `sitemap.xml`),
114      // Some of the search engines only track the first N items from the sitemap,
115      // this makes sure our starting and general guides are first, and API index last (in order from new to old)
116      pathsPriority: [
117        ...navigation.homeDirectories,
118        ...navigation.learnDirectories,
119        ...navigation.generalDirectories,
120        ...navigation.referenceDirectories.filter(dir => dir === 'versions'),
121        ...VERSIONS.map(version => `versions/${version}`),
122      ],
123      // Some of our pages are "hidden" and should not be added to the sitemap
124      pathsHidden: [...navigation.previewDirectories, ...navigation.archiveDirectories],
125    });
126    NextLog.info(`�� Generated sitemap with ${sitemapEntries.length} entries`);
127
128    return pathMap;
129  },
130  async headers() {
131    const cacheHeaders = [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }];
132    return [{ source: '/_next/static/:static*', headers: cacheHeaders }];
133  },
134};
135