Skip to content

Commit

Permalink
feat: migrate React.dev to the App Router (#7437)
Browse files Browse the repository at this point in the history
* update version to latest + move folders around

* getting home page working

* make the mdx setup work

* bypass mdxname

* split out mdx components

* re-add meta mdx logic

* replace mdxName usage

* fix code blocks

* fix max width

* convert mdx post processing to actual plugins

* fix tailwind

* fix incorrect iframe props

* cleanup mdx dic

* make it actually build

* align fonts

* fix uwu script

* fix search

* remove _app

* make it actually build

* replace next-watch-remote with custom setup

* clean up logs + clean up inline scripts

* add rss handler

* remove rss generation

* remove rss generation

* support MDX components for TOC

* clean up log + bump cache

* fix toc

* add back translations + add new overlay

* use MDX link instead of Next.js links for translation

* fix analytics

* add myself to the contributors

* fix blinking sidebar

* avoid rendering toc on the client

* plugin metadata

* simplify metadata

* fix title

* clean up metadata

* add back error decoder

* Update src/content/learn/index.md

---------

Co-authored-by: Ricky <[email protected]>
  • Loading branch information
feedthejim and rickhanlonii authored Feb 1, 2025
1 parent 3bb7a4e commit 0eb0f88
Show file tree
Hide file tree
Showing 70 changed files with 1,805 additions and 1,635 deletions.
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
"react-hooks/exhaustive-deps": "error",
"react/no-unknown-property": ["error", {"ignore": ["meta"]}],
"react-compiler/react-compiler": "error"
"react-compiler/react-compiler": "error",
"@next/next/no-img-element": "off",
"@next/next/no-html-link-for-pages": "off"
},
"env": {
"node": true,
Expand Down
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
3 changes: 3 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ const nextConfig = {
experimental: {
scrollRestoration: true,
reactCompiler: true,
newDevOverlay: true,
},

env: {},
serverExternalPackages: [],
webpack: (config, {dev, isServer, ...options}) => {
if (process.env.ANALYZE) {
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "CC",
"scripts": {
"analyze": "ANALYZE=true next build",
"dev": "next-remote-watch ./src/content",
"dev": "next dev",
"build": "next build && node --experimental-modules ./scripts/downloadFonts.mjs",
"lint": "next lint",
"lint:fix": "next lint --fix",
Expand All @@ -15,32 +15,33 @@
"prettier:diff": "yarn nit:source",
"lint-heading-ids": "node scripts/headingIdLinter.js",
"fix-headings": "node scripts/headingIdLinter.js --fix",
"ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss",
"ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids",
"tsc": "tsc --noEmit",
"start": "next start",
"postinstall": "is-ci || husky install .husky",
"check-all": "npm-run-all prettier lint:fix tsc rss",
"rss": "node scripts/generateRss.js"
"check-all": "npm-run-all prettier lint:fix tsc"
},
"dependencies": {
"@codesandbox/sandpack-react": "2.13.5",
"@docsearch/css": "^3.8.3",
"@docsearch/react": "^3.8.3",
"@headlessui/react": "^1.7.0",
"@radix-ui/react-context-menu": "^2.1.5",
"@types/mdast": "^4.0.4",
"body-scroll-lock": "^3.1.3",
"classnames": "^2.2.6",
"date-fns": "^2.16.1",
"debounce": "^1.2.1",
"github-slugger": "^1.3.0",
"next": "15.1.0",
"next": "^15.2.0-canary.33",
"next-remote-watch": "^1.0.0",
"parse-numeric-range": "^1.2.0",
"react": "^19.0.0",
"react-collapsed": "4.0.4",
"react-dom": "^19.0.0",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1"
"remark-gfm": "^3.0.1",
"unist-builder": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.9",
Expand All @@ -62,6 +63,7 @@
"autoprefixer": "^10.4.2",
"babel-eslint": "10.x",
"babel-plugin-react-compiler": "19.0.0-beta-e552027-20250112",
"chokidar": "^4.0.3",
"eslint": "7.x",
"eslint-config-next": "12.0.3",
"eslint-config-react-app": "^5.2.1",
Expand Down
6 changes: 0 additions & 6 deletions scripts/generateRss.js

This file was deleted.

172 changes: 172 additions & 0 deletions src/app/[[...markdownPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import fs from 'fs/promises';
import path from 'path';
import {Page} from 'components/Layout/Page';
import sidebarHome from '../../sidebarHome.json';
import sidebarLearn from '../../sidebarLearn.json';
import sidebarReference from '../../sidebarReference.json';
import sidebarCommunity from '../../sidebarCommunity.json';
import sidebarBlog from '../../sidebarBlog.json';
import {generateMDX} from '../../utils/generateMDX';

import {generateMetadata as generateSeoMetadata} from '../../utils/generateMetadata';

import {getRouteMeta, RouteItem} from 'components/Layout/getRouteMeta';
import {LanguageItem} from 'components/MDX/LanguagesContext';
import {cache} from 'react';

function getActiveSection(pathname: string) {
if (pathname === '/') {
return 'home';
} else if (pathname.startsWith('/reference')) {
return 'reference';
} else if (pathname.startsWith('/learn')) {
return 'learn';
} else if (pathname.startsWith('/community')) {
return 'community';
} else if (pathname.startsWith('/blog')) {
return 'blog';
} else {
return 'unknown';
}
}

async function getRouteTree(section: string) {
switch (section) {
case 'home':
case 'unknown':
return sidebarHome;
case 'learn':
return sidebarLearn;
case 'reference':
return sidebarReference;
case 'community':
return sidebarCommunity;
case 'blog':
return sidebarBlog;
default:
throw new Error(`Unknown section: ${section}`);
}
}

const getPageContent = cache(async function getPageContent(
markdownPath: any[]
) {
const rootDir = path.join(process.cwd(), 'src/content');
let mdxPath = markdownPath?.join('/') || 'index';
let mdx;

try {
mdx = await fs.readFile(path.join(rootDir, mdxPath + '.md'), 'utf8');
} catch {
mdx = await fs.readFile(path.join(rootDir, mdxPath, 'index.md'), 'utf8');
}

return await generateMDX(mdx, mdxPath, {});
});

// This replaces getStaticPaths
export async function generateStaticParams() {
const rootDir = path.join(process.cwd(), 'src/content');

async function getFiles(dir: string): Promise<string[]> {
const entries = await fs.readdir(dir, {withFileTypes: true});
const files = await Promise.all(
entries.map(async (entry) => {
const res = path.resolve(dir, entry.name);
return entry.isDirectory()
? getFiles(res)
: res.slice(rootDir.length + 1);
})
);

return files
.flat()
.filter(
(file: string) => file.endsWith('.md') && !file.startsWith('errors/')
);
}

function getSegments(file: string) {
let segments = file.slice(0, -3).replace(/\\/g, '/').split('/');
if (segments[segments.length - 1] === 'index') {
segments.pop();
}
return segments;
}

const files = await getFiles(rootDir);

return files.map((file: any) => ({
markdownPath: getSegments(file),
}));
}

export default async function WrapperPage({
params,
}: {
params: Promise<{markdownPath: string[]}>;
}) {
const {markdownPath} = await params;

// Get the MDX content and associated data
const {content, toc, meta} = await getPageContent(markdownPath);

const pathname = '/' + (markdownPath?.join('/') || '');
const section = getActiveSection(pathname);
const routeTree = await getRouteTree(section);

// Load the list of translated languages conditionally.
let languages: Array<LanguageItem> | null = null;
if (pathname.endsWith('/translations')) {
languages = await (
await fetch(
'https://raw.githubusercontent.com/reactjs/translations.react.dev/main/langs/langs.json'
)
).json(); // { code: string; name: string; enName: string}[]
}

// Pass the content and TOC directly, as `getPageContent` should already return them in the correct format
return (
<Page
toc={toc} // Pass the TOC directly without parsing
routeTree={routeTree as RouteItem}
meta={meta}
section={section}
pathname={pathname}
languages={languages}>
{content}
</Page>
);
}
// Configure dynamic segments to be statically generated
export const dynamicParams = false;

export async function generateMetadata({
params,
}: {
params: Promise<{markdownPath: string[]}>;
}) {
const {markdownPath} = await params;
const pathname = '/' + (markdownPath?.join('/') || '');
const section = getActiveSection(pathname);
const routeTree = await getRouteTree(section);
const {route, order} = getRouteMeta(pathname, routeTree as RouteItem);
const {
title = route?.title || '',
description = route?.description || '',
titleForTitleTag,
} = await getPageContent(markdownPath).then(({meta}) => meta);

return generateSeoMetadata({
title,
isHomePage: pathname === '/',
path: pathname,
description,
titleForTitleTag,
image: `/images/og-${section}.png`,
searchOrder:
section === 'learn' || (section === 'blog' && pathname !== '/blog')
? order
: undefined,
});
}
18 changes: 16 additions & 2 deletions src/pages/500.js → src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
'use client';

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/

import {Page} from 'components/Layout/Page';
import {MDXComponents} from 'components/MDX/MDXComponents';
import sidebarLearn from '../sidebarLearn.json';
import {RouteItem} from 'components/Layout/getRouteMeta';
import {generateMetadata as generateSeoMetadata} from 'utils/generateMetadata';

const {Intro, MaxWidth, p: P, a: A} = MDXComponents;

export default function NotFound() {
export default function Error() {
return (
<Page
section="unknown"
toc={[]}
routeTree={sidebarLearn}
pathname="/500"
routeTree={sidebarLearn as RouteItem}
meta={{title: 'Something Went Wrong'}}>
<MaxWidth>
<Intro>
Expand All @@ -29,3 +35,11 @@ export default function NotFound() {
</Page>
);
}

export async function generateMetadata({}: {}) {
return generateSeoMetadata({
title: 'Something Went Wrong',
isHomePage: false,
path: '/500',
});
}
Loading

0 comments on commit 0eb0f88

Please sign in to comment.