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