diff --git a/package.json b/package.json
index 00e59f79..82e34862 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"@babel/preset-react": "^7.18.6",
"@mdx-js/mdx": "^2.1.3",
"@resvg/resvg-js": "^2.6.2",
+ "@shuding/opentype.js": "1.4.0-beta.0",
"@types/body-scroll-lock": "^2.6.1",
"@types/classnames": "^2.2.10",
"@types/debounce": "^1.2.1",
diff --git a/public/images/team/gh-rh389.jpg b/public/images/team/gh-robhogan.jpg
similarity index 100%
rename from public/images/team/gh-rh389.jpg
rename to public/images/team/gh-robhogan.jpg
diff --git a/scripts/generateOgImages.mjs b/scripts/generateOgImages.mjs
index 937727a1..bc805d38 100644
--- a/scripts/generateOgImages.mjs
+++ b/scripts/generateOgImages.mjs
@@ -14,6 +14,7 @@ import path from 'path';
import satori from 'satori';
import {Resvg} from '@resvg/resvg-js';
import matter from 'gray-matter';
+import opentype from '@shuding/opentype.js';
const ROOT = process.cwd();
const CONTENT_DIR = path.join(ROOT, 'src', 'content');
@@ -34,6 +35,33 @@ const medium = fs.readFileSync(
path.join(ROOT, 'public', 'fonts', 'Optimistic_Display_W_Md.ttf')
);
+// Title area width: card width minus the horizontal padding (80px per side).
+const TITLE_MAX_WIDTH = 1200 - 80 * 2;
+const TITLE_MAX_FONT = 96;
+const TITLE_MIN_FONT = 56;
+// Small margin so borderline titles don't wrap and orphan a single trailing
+// character (e.g. the "p" in "renderToStaticMarkup", which is 1px too wide
+// to fit on one line at 96px).
+const TITLE_SAFETY = 8;
+
+const boldFont = opentype.parse(
+ bold.buffer.slice(bold.byteOffset, bold.byteOffset + bold.byteLength)
+);
+
+// Multi-word titles wrap cleanly at spaces, so a length bucket is fine for
+// them. Single-word titles (most API names) can only break mid-word, which
+// leaves an orphaned letter on its own line, so instead shrink them just
+// enough to fit on a single line.
+function titleFontSize(title) {
+ const trimmed = title.trim();
+ if (/\s/.test(trimmed)) {
+ return trimmed.length > 24 ? 72 : 96;
+ }
+ const widthPerPx = boldFont.getAdvanceWidth(trimmed, 1);
+ const fit = Math.floor((TITLE_MAX_WIDTH - TITLE_SAFETY) / widthPerPx);
+ return Math.max(TITLE_MIN_FONT, Math.min(TITLE_MAX_FONT, fit));
+}
+
function el(type, style, children) {
return {type, props: {style, children}};
}
@@ -103,7 +131,7 @@ function card(title, pagePath) {
flexGrow: 1,
display: 'flex',
alignItems: 'center',
- fontSize: title.length > 24 ? 72 : 96,
+ fontSize: titleFontSize(title),
fontFamily: 'Optimistic Display Bold',
color: '#f6f7f9',
lineHeight: 1.1,
diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md
index 2e95e24e..f3ae269b 100644
--- a/src/content/community/conferences.md
+++ b/src/content/community/conferences.md
@@ -10,27 +10,41 @@ Do you know of a local React.js conference? Add it here! (Please keep the list c
## Upcoming Conferences {/*upcoming-conferences*/}
-### React Paris 2026 {/*react-paris-2026*/}
-March 26 - 27, 2026. In-person in Paris, France (hybrid event)
+### ZurichJS Conf 2026 {/*zurichjs-conf-2026*/}
+September 10-11, 2026. In-person in Zurich, Switzerland
-[Website](https://react.paris/) - [Twitter](https://x.com/BeJS_)
+[Website](https://conf.zurichjs.com?utm_campaign=ZurichJS_Conf&utm_source=referral&utm_content=reactjs_community_conferences) - [Twitter](https://x.com/zurichjs) - [LinkedIn](https://www.linkedin.com/company/zurichjs/)
-### CityJS London 2026 {/*cityjs-london-2026*/}
-April 14-17, 2026. In-person in London
+### React Alicante 2026 {/*react-alicante-2026*/}
+Sep 24 - 26, 2026. In-person in Alicante
-[Website](https://india.cityjsconf.org/) - [Twitter](https://x.com/cityjsconf) - [Bluesky](https://bsky.app/profile/cityjsconf.bsky.social)
+[Website](https://reactalicante.es//) - [Twitter](https://x.com/ReactAlicante)
-### ZurichJS Conf 2026 {/*zurichjs-conf-2026*/}
-September 10-11, 2026. In-person in Zurich, Switzerland
+### CityJS Athens 2026 {/*cityjs-athens-2026*/}
+October 21-23, 2026. In-person in Athens
+
+[Website](https://athens.cityjsconf.org/) - [Twitter](https://x.com/cityjsconf) - [Bluesky](https://bsky.app/profile/cityjsconf.bsky.social)
+
+
+## Past Conferences {/*past-conferences*/}
-[Website](https://conf.zurichjs.com?utm_campaign=ZurichJS_Conf&utm_source=referral&utm_content=reactjs_community_conferences) - [Twitter](https://x.com/zurichjs) - [LinkedIn](https://www.linkedin.com/company/zurichjs/)
### React Conf Japan 2027 {/*react-conf-japan-2027*/}
April 24, 2027. In-person in Tokyo, Japan
[Website](https://reactconf.jp/) - [Twitter](https://x.com/reactconfjp)
-## Past Conferences {/*past-conferences*/}
+### CityJS London 2026 {/*cityjs-london-2026*/}
+April 14-17, 2026. In-person in London
+
+[Website](https://india.cityjsconf.org/) - [Twitter](https://x.com/cityjsconf) - [Bluesky](https://bsky.app/profile/cityjsconf.bsky.social)
+
+
+### React Paris 2026 {/*react-paris-2026*/}
+March 26 - 27, 2026. In-person in Paris, France (hybrid event)
+
+[Website](https://react.paris/) - [Twitter](https://x.com/BeJS_)
+
### CityJS New Delhi 2026 {/*cityjs-newdelhi-2026*/}
February 12-13, 2026. In-person in New Delhi, India
diff --git a/src/content/community/team.md b/src/content/community/team.md
index e321f240..dd77a539 100644
--- a/src/content/community/team.md
+++ b/src/content/community/team.md
@@ -26,7 +26,7 @@ React work is organized into working groups, each responsible for an area of the
-
+
Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template.
@@ -38,7 +38,7 @@ React work is organized into working groups, each responsible for an area of the
## Working Group members {/*working-group-members*/}
-
+
@@ -82,11 +82,11 @@ React work is organized into working groups, each responsible for an area of the
-
+
-
+
-
+
Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes.
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index 7388efa1..a5187326 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -662,7 +662,7 @@ This cache pattern is the foundation for [re-fetching data](#re-fetching-data-in
-Don't skip calling `use` based on whether a Promise is already settled.
+##### Don't skip calling `use` based on whether a Promise is already settled. {/*conditional-use*/}
Unlike other hooks, `use` can be called inside conditions and loops — but it must always be called for the Promise itself. Never read `promise.status` or `promise.value` directly to bypass `use`; always pass the Promise to `use` and let React handle it.
diff --git a/src/content/reference/react/useDeferredValue.md b/src/content/reference/react/useDeferredValue.md
index 1f85dcb2..f5aa29bb 100644
--- a/src/content/reference/react/useDeferredValue.md
+++ b/src/content/reference/react/useDeferredValue.md
@@ -88,7 +88,7 @@ During updates, the deferred value will "lag behin
This example assumes you use a data source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise you read with [`use`](/reference/react/use).
-[Learn more about Suspense and its limitations.](/reference/react/Suspense)
+[Learn more about Suspense.](/reference/react/Suspense)
diff --git a/vercel.json b/vercel.json
index 87d006de..2aa3316c 100644
--- a/vercel.json
+++ b/vercel.json
@@ -174,6 +174,11 @@
"destination": "/warnings/invalid-hook-call-warning#mismatching-versions-of-react-and-react-dom",
"permanent": false
},
+ {
+ "source": "/warnings/conditional-use-of-use",
+ "destination": "/reference/react/use#conditional-use",
+ "permanent": false
+ },
{
"source": "/reference/react/directives",
"destination": "/reference/rsc/directives",