Skip to content

Commit 8c8274f

Browse files
authored
Merge pull request github#19425 from github/react-head-2
React head data and other fixes
2 parents 7b73a32 + 385b159 commit 8c8274f

14 files changed

+99
-52
lines changed

.github/CODEOWNERS

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
# Engineering
77
*.js @github/docs-engineering
8+
*.ts @github/docs-engineering
9+
*.tsx @github/docs-engineering
810
/.github/ @github/docs-engineering
911
/script/ @github/docs-engineering
1012
/includes/ @github/docs-engineering

components/Contribution.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const Contribution = () => {
2020
</a>
2121
<p className="color-text-secondary f6 mt-2">
2222
{t`or`}{' '}
23-
<a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank">
23+
<a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener">
2424
{t`to_guidelines`}
2525
</a>
2626
</p>

components/DefaultLayout.tsx

+28-1
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,43 @@ import { ScrollButton } from 'components/ScrollButton'
77
import { SupportSection } from 'components/SupportSection'
88
import { DeprecationBanner } from 'components/DeprecationBanner'
99
import { useMainContext } from 'components/context/MainContext'
10+
import { useTranslation } from './hooks/useTranslation'
11+
import { useVersion } from './hooks/useVersion'
1012

1113
type Props = { children?: React.ReactNode }
1214
export const DefaultLayout = (props: Props) => {
13-
const { builtAssets, expose } = useMainContext()
15+
const { builtAssets, expose, page, error } = useMainContext()
16+
const { currentVersion } = useVersion()
17+
const { t } = useTranslation('errors')
1418
return (
1519
<div className="d-lg-flex">
1620
<Head>
21+
{error === '404' ? (
22+
<title>{t('oops')}</title>
23+
) : currentVersion !== 'homepage' && page.fullTitle ? (
24+
<title>{page.fullTitle}</title>
25+
) : null}
26+
1727
<link rel="stylesheet" href={builtAssets.main.css} />
1828
<script id="expose" type="application/json" dangerouslySetInnerHTML={{ __html: expose }} />
1929
<script src={builtAssets.main.js} />
30+
31+
{/* For Google and Bots */}
32+
{page.introPlainText && <meta name="description" content={page.introPlainText} />}
33+
34+
{page.topics.length > 0 && <meta name="keywords" content={page.topics.join(',')} />}
35+
36+
{page.hidden && <meta name="robots" content="noindex" />}
37+
38+
{page.languageVariants.map((languageVariant) => {
39+
return (
40+
<link
41+
rel="alternate"
42+
hrefLang={languageVariant.hreflang}
43+
href={`https://docs.github.com${languageVariant.href}`}
44+
/>
45+
)
46+
})}
2047
</Head>
2148
<SidebarNav />
2249

components/Header.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const Header = () => {
5050
<div className="width-full">
5151
<div className="d-inline-block width-full d-md-flex" style={{ zIndex: 1 }}>
5252
<div className="float-right d-md-none position-relative" style={{ zIndex: 3 }}>
53-
<ButtonOutline css onClick={() => setIsMenuOpen(!isMenuOpen)}>
53+
<ButtonOutline css onClick={() => setIsMenuOpen(!isMenuOpen)} aria-label="Navigation Menu">
5454
{isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />}
5555
</ButtonOutline>
5656
</div>

components/context/MainContext.tsx

+33-20
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,22 @@ export type MainContextT = {
103103
productSiteTree?: ProductSiteTree
104104
productSiteTreeNew?: SiteTreePage
105105
featureFlags: FeatureFlags
106-
pageHidden: boolean
107-
pagePermalinks?: Array<{
108-
languageCode: string
109-
relativePath: string
110-
title: string
111-
pageVersionTitle: string
112-
pageVersion: string
113-
href: string
114-
}>
106+
page: {
107+
languageVariants: Array<{ name: string; code: string; hreflang: string; href: string }>
108+
topics: Array<string>
109+
fullTitle?: string
110+
introPlainText?: string
111+
hidden: boolean
112+
permalinks?: Array<{
113+
languageCode: string
114+
relativePath: string
115+
title: string
116+
pageVersionTitle: string
117+
pageVersion: string
118+
href: string
119+
}>
120+
}
121+
115122
enterpriseServerVersions: Array<string>
116123
}
117124

@@ -137,17 +144,23 @@ export const getMainContextFromRequest = (req: any): MainContextT => {
137144
airGap: req.context.AIRGAP || false,
138145
currentCategory: req.context.currentCategory || '',
139146
relativePath: req.context.page?.relativePath,
140-
pagePermalinks: req.context.page?.permalinks.map((obj: any) =>
141-
pick(obj, [
142-
'title',
143-
'pageVersionTitle',
144-
'pageVersion',
145-
'href',
146-
'relativePath',
147-
'languageCode',
148-
])
149-
),
150-
pageHidden: req.context.page.hidden || false,
147+
page: {
148+
languageVariants: req.context.page.languageVariants,
149+
fullTitle: req.context.page.fullTitle,
150+
topics: req.context.page.topics || [],
151+
introPlainText: req.context.page?.introPlainText,
152+
permalinks: req.context.page?.permalinks.map((obj: any) =>
153+
pick(obj, [
154+
'title',
155+
'pageVersionTitle',
156+
'pageVersion',
157+
'href',
158+
'relativePath',
159+
'languageCode',
160+
])
161+
),
162+
hidden: req.context.page.hidden || false,
163+
},
151164
enterpriseServerReleases: JSON.parse(JSON.stringify(req.context.enterpriseServerReleases)),
152165
enterpriseServerVersions: req.context.enterpriseServerVersions,
153166
currentLanguage: req.context.currentLanguage,

components/landing/FeaturedArticles.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const ArticleList = ({ title, viewAllHref, articles }: ArticleListProps) => {
6161
return (
6262
<>
6363
<div className="featured-links-heading mb-4 d-flex flex-items-baseline">
64-
<h3 className="f4 text-normal text-mono text-uppercase color-text-secondary">{title}</h3>
64+
<h3 className="f4 text-normal text-mono text-uppercase">{title}</h3>
6565
{viewAllHref && (
6666
<Link href={viewAllHref}>
6767
<a className="ml-4">

components/landing/HomepageVersionPicker.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { useTranslation } from 'components/hooks/useTranslation'
99
export const HomepageVersionPicker = () => {
1010
const router = useRouter()
1111
const { currentVersion } = useVersion()
12-
const { allVersions, pagePermalinks, enterpriseServerVersions } = useMainContext()
12+
const { allVersions, page, enterpriseServerVersions } = useMainContext()
1313
const { t } = useTranslation('homepage')
1414

15-
if (!pagePermalinks || pagePermalinks.length <= 1) {
15+
if (page.permalinks && page.permalinks.length <= 1) {
1616
return null
1717
}
1818

@@ -34,7 +34,7 @@ export const HomepageVersionPicker = () => {
3434
<Dropdown.Caret />
3535
</summary>
3636
<Dropdown.Menu direction="sw">
37-
{pagePermalinks.map((permalink) => {
37+
{(page.permalinks || []).map((permalink) => {
3838
if (permalink.pageVersion === 'homepage') {
3939
return null
4040
}

components/landing/LandingHero.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from 'react'
12
import cx from 'classnames'
23
import Link from 'next/link'
34
import { useRouter } from 'next/router'
@@ -11,6 +12,12 @@ export const LandingHero = () => {
1112
const { airGap } = useMainContext()
1213
const { product_video, shortTitle, beta_product, intro, introLinks } = useProductLandingContext()
1314
const { t } = useTranslation('product_landing')
15+
const [renderIFrame, setRenderIFrame] = useState(false)
16+
17+
// delay iFrame rendering so that dom ready happens sooner
18+
useEffect(() => {
19+
setRenderIFrame(true)
20+
}, [])
1421

1522
return (
1623
<header className="d-lg-flex gutter-lg mb-6">
@@ -73,7 +80,7 @@ export const LandingHero = () => {
7380
<iframe
7481
title={`${shortTitle} Video`}
7582
className="top-0 left-0 position-absolute color-shadow-large rounded-1 width-full height-full"
76-
src={product_video}
83+
src={renderIFrame ? product_video : ''}
7784
frameBorder="0"
7885
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
7986
allowFullScreen

components/lib/getThemeProps.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const getThemeProps = (req: any, mode?: 'css') => {
2323
} = {}
2424
const defaultProps = mode === 'css' ? defaultCSSThemeProps : defaultThemeProps
2525

26-
if (req.cookies.color_mode) {
26+
if (req.cookies?.color_mode) {
2727
try {
2828
cookieValue = JSON.parse(decodeURIComponent(req.cookies.color_mode))
2929
} catch {

components/product/ProductSiteTree.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ also gets an `is-current-page` class.
1313
*/
1414
export const ProductSiteTree = () => {
1515
const router = useRouter()
16-
const { productSiteTree: product, pageHidden, breadcrumbs } = useMainContext()
16+
const { productSiteTree: product, page, breadcrumbs } = useMainContext()
1717
if (!product) {
1818
return null
1919
}
@@ -23,7 +23,7 @@ export const ProductSiteTree = () => {
2323
<AllProductsLink />
2424

2525
<li title={product.title} className="sidebar-product mb-2">
26-
{!pageHidden && (
26+
{!page.hidden && (
2727
<Link href={product.href}>
2828
<a className="pl-4 pr-5 pb-1 f4 color-text-primary">{product.title}</a>
2929
</Link>

middleware/render-page.js

+3-13
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,6 @@ const pageCache = new RedisAccessor({
2626
// a list of query params that *do* alter the rendered page, and therefore should be cached separately
2727
const cacheableQueries = ['learn']
2828

29-
const renderWithNext = FEATURE_NEXTJS
30-
? [
31-
'/en/rest',
32-
'/en/sponsors',
33-
'/ja/sponsors',
34-
'/en/discussions',
35-
'/en/actions'
36-
]
37-
: []
38-
3929
function modifyOutput (req, text) {
4030
return addColorMode(req, addCsrf(req, text))
4131
}
@@ -101,9 +91,9 @@ module.exports = async function renderPage (req, res, next) {
10191
const isRequestingJsonForDebugging = 'json' in req.query && process.env.NODE_ENV !== 'production'
10292

10393
// Should the current path be rendered by NextJS?
104-
const isNextJsRequest = renderWithNext.includes(req.path)
94+
const renderWithNextjs = 'nextjs' in req.query && FEATURE_NEXTJS
10595

106-
if (isCacheable && !isRequestingJsonForDebugging && !(FEATURE_NEXTJS && isNextJsRequest)) {
96+
if (isCacheable && !isRequestingJsonForDebugging && !renderWithNextjs) {
10797
// Stop processing if the connection was already dropped
10898
if (isConnectionDropped(req, res)) return
10999

@@ -174,7 +164,7 @@ module.exports = async function renderPage (req, res, next) {
174164
}
175165
}
176166

177-
if (FEATURE_NEXTJS && isNextJsRequest) {
167+
if (renderWithNextjs) {
178168
nextHandleRequest(req, res)
179169
} else {
180170
// currentLayout is added to the context object in middleware/contextualizers/layouts

pages/[versionId]/[productId]/index.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
ProductLandingContext,
1313
useProductLandingContext,
1414
} from 'components/context/ProductLandingContext'
15-
import { getThemeProps } from 'components/lib/getThemeProps'
1615

1716
import { LandingHero } from 'components/landing/LandingHero'
1817
import { FeaturedArticles } from 'components/landing/FeaturedArticles'
@@ -96,7 +95,6 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
9695

9796
return {
9897
props: {
99-
themeProps: getThemeProps(req),
10098
mainContext: getMainContextFromRequest(req),
10199
productLandingContext: getProductLandingContextFromRequest(req),
102100
},

pages/_app.tsx

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import React, { useEffect } from 'react'
2-
import { AppProps } from 'next/app'
2+
import App from 'next/app'
3+
import type { AppProps, AppContext } from 'next/app'
34
import Head from 'next/head'
45
import { useTheme, ThemeProvider } from '@primer/components'
6+
import { getThemeProps } from 'components/lib/getThemeProps'
57

68
import '@primer/css/index.scss'
79

810
import { defaultThemeProps } from 'components/lib/getThemeProps'
911

10-
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
12+
type MyAppProps = AppProps & { csrfToken: string, themeProps: typeof defaultThemeProps }
13+
const MyApp = ({ Component, pageProps, csrfToken, themeProps }: MyAppProps) => {
1114
return (
1215
<>
1316
<Head>
@@ -27,16 +30,24 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
2730
content="c1kuD-K2HIVF635lypcsWPoD4kilo5-jA_wBFyT4uMY"
2831
/>
2932

30-
<meta name="csrf-token" content="$CSRFTOKEN$" />
33+
<meta name="csrf-token" content={csrfToken} />
3134
</Head>
3235
<ThemeProvider>
33-
<SetTheme themeProps={pageProps.themeProps} />
36+
<SetTheme themeProps={themeProps} />
3437
<Component {...pageProps} />
3538
</ThemeProvider>
3639
</>
3740
)
3841
}
3942

43+
MyApp.getInitialProps = async (appContext: AppContext) => {
44+
const { ctx } = appContext
45+
// calls page's `getInitialProps` and fills `appProps.pageProps`
46+
const appProps = await App.getInitialProps(appContext);
47+
48+
return { ...appProps, themeProps: getThemeProps(ctx.req), csrfToken: (ctx.req as any).csrfToken() }
49+
}
50+
4051
const SetTheme = ({ themeProps }: { themeProps: typeof defaultThemeProps }) => {
4152
// Cause primer/components to re-evaluate the 'auto' color mode on client side render
4253
const { setColorMode } = useTheme()
@@ -48,4 +59,4 @@ const SetTheme = ({ themeProps }: { themeProps: typeof defaultThemeProps }) => {
4859
return null
4960
}
5061

51-
export default App
62+
export default MyApp

pages/_document.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export default class MyDocument extends Document {
1919
return {
2020
...initialProps,
2121
cssThemeProps: getThemeProps(ctx.req, 'css'),
22-
themeProps: getThemeProps(ctx.req),
2322
styles: (
2423
<>
2524
{initialProps.styles}

0 commit comments

Comments
 (0)