Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate React.dev to the App Router #7437

Merged
merged 39 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7e62576
update version to latest + move folders around
feedthejim Jan 19, 2025
afc2f86
getting home page working
feedthejim Jan 19, 2025
82ce63a
make the mdx setup work
feedthejim Jan 19, 2025
84e4e75
bypass mdxname
feedthejim Jan 19, 2025
521e058
split out mdx components
feedthejim Jan 19, 2025
78eb453
re-add meta mdx logic
feedthejim Jan 19, 2025
227ca33
replace mdxName usage
feedthejim Jan 19, 2025
58893e2
fix code blocks
feedthejim Jan 19, 2025
bb73bef
fix max width
feedthejim Jan 19, 2025
5f0c400
convert mdx post processing to actual plugins
feedthejim Jan 19, 2025
54ed2f7
fix tailwind
feedthejim Jan 19, 2025
da986d3
fix incorrect iframe props
feedthejim Jan 19, 2025
57e93aa
cleanup mdx dic
feedthejim Jan 19, 2025
c388c7a
make it actually build
feedthejim Jan 19, 2025
07a6965
align fonts
feedthejim Jan 19, 2025
fbc4ad3
fix uwu script
feedthejim Jan 19, 2025
c240a23
fix search
feedthejim Jan 19, 2025
2d21046
remove _app
feedthejim Jan 19, 2025
abd3e7a
make it actually build
feedthejim Jan 19, 2025
c17d55b
replace next-watch-remote with custom setup
feedthejim Jan 31, 2025
5992b25
clean up logs + clean up inline scripts
feedthejim Jan 31, 2025
6266d2a
add rss handler
feedthejim Jan 31, 2025
abe7942
remove rss generation
feedthejim Jan 31, 2025
01b2bdd
remove rss generation
feedthejim Jan 31, 2025
c4840c9
support MDX components for TOC
feedthejim Jan 31, 2025
c6b7f38
clean up log + bump cache
feedthejim Jan 31, 2025
ce85254
fix toc
feedthejim Jan 31, 2025
4bad08d
add back translations + add new overlay
feedthejim Jan 31, 2025
c0961e4
use MDX link instead of Next.js links for translation
feedthejim Jan 31, 2025
5617362
fix analytics
feedthejim Jan 31, 2025
6bca962
add myself to the contributors
feedthejim Jan 31, 2025
662ca7e
fix blinking sidebar
feedthejim Jan 31, 2025
cb7be41
avoid rendering toc on the client
feedthejim Jan 31, 2025
74a6251
plugin metadata
feedthejim Jan 31, 2025
d623e65
simplify metadata
feedthejim Jan 31, 2025
cc15fe1
fix title
feedthejim Jan 31, 2025
552ce46
clean up metadata
feedthejim Jan 31, 2025
58ce525
add back error decoder
feedthejim Jan 31, 2025
2638b6e
Update src/content/learn/index.md
rickhanlonii Feb 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a server component?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/*
* 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