Skip to content

Commit 61e791f

Browse files
authored
Page meta tags are not generated on the server (#2731)
* Page meta tags are not generated on the server Fixes #2730 * suppress warnings about critical dependencies in Next.js logs
1 parent 53d02c5 commit 61e791f

File tree

12 files changed

+193
-138
lines changed

12 files changed

+193
-138
lines changed

lib/growthbook/useLoadFeatures.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
import type { GrowthBook } from '@growthbook/growthbook-react';
12
import React from 'react';
23

34
import { SECOND } from 'toolkit/utils/consts';
45

5-
import { initGrowthBook } from './init';
6-
7-
export default function useLoadFeatures(uuid: string) {
8-
const growthBook = initGrowthBook(uuid);
6+
export default function useLoadFeatures(growthBook: GrowthBook | undefined) {
97
React.useEffect(() => {
10-
growthBook?.setAttributes({
8+
if (!growthBook) {
9+
return;
10+
}
11+
12+
growthBook.setAttributes({
1113
...growthBook.getAttributes(),
1214
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1315
language: window.navigator.language,
1416
});
1517

16-
growthBook?.loadFeatures({ timeout: SECOND });
18+
growthBook.loadFeatures({ timeout: SECOND });
1719
}, [ growthBook ]);
1820
}

next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const moduleExports = {
3939
headers,
4040
output: 'standalone',
4141
productionBrowserSourceMaps: true,
42+
serverExternalPackages: ["@opentelemetry/sdk-node", "@opentelemetry/auto-instrumentations-node"],
4243
experimental: {
4344
staleTimes: {
4445
dynamic: 30,

nextjs/PageMetadata.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Head from 'next/head';
2+
import React from 'react';
3+
4+
import type { Route } from 'nextjs-routes';
5+
import type { Props as PageProps } from 'nextjs/getServerSideProps';
6+
7+
import config from 'configs/app';
8+
import * as metadata from 'lib/metadata';
9+
10+
interface Props<Pathname extends Route['pathname']> {
11+
pathname: Pathname;
12+
query?: PageProps<Pathname>['query'];
13+
apiData?: PageProps<Pathname>['apiData'];
14+
}
15+
16+
const PageMetadata = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
17+
const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData);
18+
19+
return (
20+
<Head>
21+
<title>{ title }</title>
22+
<meta name="description" content={ description }/>
23+
{ canonical && <link rel="canonical" href={ canonical }/> }
24+
25+
{ /* OG TAGS */ }
26+
<meta property="og:title" content={ opengraph.title }/>
27+
{ opengraph.description && <meta property="og:description" content={ opengraph.description }/> }
28+
<meta property="og:image" content={ opengraph.imageUrl }/>
29+
<meta property="og:type" content="website"/>
30+
31+
{ /* Twitter Meta Tags */ }
32+
<meta name="twitter:card" content="summary_large_image"/>
33+
<meta property="twitter:domain" content={ config.app.host }/>
34+
<meta name="twitter:title" content={ opengraph.title }/>
35+
{ opengraph.description && <meta name="twitter:description" content={ opengraph.description }/> }
36+
<meta property="twitter:image" content={ opengraph.imageUrl }/>
37+
</Head>
38+
);
39+
};
40+
41+
export default PageMetadata;

nextjs/PageNextJs.tsx

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import Head from 'next/head';
21
import React from 'react';
32

43
import type { Route } from 'nextjs-routes';
54
import type { Props as PageProps } from 'nextjs/getServerSideProps';
5+
import PageMetadata from 'nextjs/PageMetadata';
66

7-
import config from 'configs/app';
87
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
98
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
10-
import * as metadata from 'lib/metadata';
9+
import useIsMounted from 'lib/hooks/useIsMounted';
10+
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
1111
import * as mixpanel from 'lib/mixpanel';
1212

1313
interface Props<Pathname extends Route['pathname']> {
@@ -18,35 +18,19 @@ interface Props<Pathname extends Route['pathname']> {
1818
}
1919

2020
const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
21-
const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData);
21+
const isMounted = useIsMounted();
2222

2323
useGetCsrfToken();
2424
useAdblockDetect();
25+
useNotifyOnNavigation();
2526

2627
const isMixpanelInited = mixpanel.useInit();
2728
mixpanel.useLogPageView(isMixpanelInited);
2829

2930
return (
3031
<>
31-
<Head>
32-
<title>{ title }</title>
33-
<meta name="description" content={ description }/>
34-
{ canonical && <link rel="canonical" href={ canonical }/> }
35-
36-
{ /* OG TAGS */ }
37-
<meta property="og:title" content={ opengraph.title }/>
38-
{ opengraph.description && <meta property="og:description" content={ opengraph.description }/> }
39-
<meta property="og:image" content={ opengraph.imageUrl }/>
40-
<meta property="og:type" content="website"/>
41-
42-
{ /* Twitter Meta Tags */ }
43-
<meta name="twitter:card" content="summary_large_image"/>
44-
<meta property="twitter:domain" content={ config.app.host }/>
45-
<meta name="twitter:title" content={ opengraph.title }/>
46-
{ opengraph.description && <meta name="twitter:description" content={ opengraph.description }/> }
47-
<meta property="twitter:image" content={ opengraph.imageUrl }/>
48-
</Head>
49-
{ props.children }
32+
<PageMetadata pathname={ props.pathname } query={ props.query } apiData={ props.apiData }/>
33+
{ isMounted ? props.children : null }
5034
</>
5135
);
5236
};

pages/_app.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
1616
import { SettingsContextProvider } from 'lib/contexts/settings';
1717
import { initGrowthBook } from 'lib/growthbook/init';
1818
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
19-
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
2019
import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/rollbar';
2120
import { SocketProvider } from 'lib/socket/context';
2221
import { Provider as ChakraProvider } from 'toolkit/chakra/provider';
@@ -49,26 +48,28 @@ const ERROR_SCREEN_STYLES: HTMLChakraProps<'div'> = {
4948
};
5049

5150
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
52-
// to avoid hydration mismatch between server and client
53-
// we have to render the app only on client (when it is mounted)
54-
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
55-
const [ mounted, setMounted ] = React.useState(false);
56-
57-
React.useEffect(() => {
58-
setMounted(true);
59-
}, []);
60-
61-
useLoadFeatures(pageProps.uuid);
62-
useNotifyOnNavigation();
6351

6452
const growthBook = initGrowthBook(pageProps.uuid);
53+
useLoadFeatures(growthBook);
54+
6555
const queryClient = useQueryClientConfig();
6656

67-
if (!mounted) {
68-
return null;
69-
}
57+
const content = (() => {
58+
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
7059

71-
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
60+
return (
61+
<>
62+
{ getLayout(<Component { ...pageProps }/>) }
63+
<Toaster/>
64+
{ config.features.rewards.isEnabled && (
65+
<>
66+
<RewardsLoginModal/>
67+
<RewardsActivityTracker/>
68+
</>
69+
) }
70+
</>
71+
);
72+
})();
7273

7374
return (
7475
<ChakraProvider>
@@ -86,14 +87,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
8687
<RewardsContextProvider>
8788
<MarketplaceContextProvider>
8889
<SettingsContextProvider>
89-
{ getLayout(<Component { ...pageProps }/>) }
90-
<Toaster/>
91-
{ config.features.rewards.isEnabled && (
92-
<>
93-
<RewardsLoginModal/>
94-
<RewardsActivityTracker/>
95-
</>
96-
) }
90+
{ content }
9791
</SettingsContextProvider>
9892
</MarketplaceContextProvider>
9993
</RewardsContextProvider>

ui/shared/layout/Layout.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@ import * as Layout from './components';
1111

1212
const LayoutDefault = ({ children }: Props) => {
1313
return (
14-
<Layout.Container>
15-
<Layout.TopRow/>
16-
<Layout.NavBar/>
17-
<HeaderMobile/>
18-
<Layout.MainArea>
19-
<Layout.SideBar/>
20-
<Layout.MainColumn>
21-
<HeaderAlert/>
22-
<HeaderDesktop/>
23-
<AppErrorBoundary>
24-
<Layout.Content>
25-
{ children }
26-
</Layout.Content>
27-
</AppErrorBoundary>
28-
</Layout.MainColumn>
29-
</Layout.MainArea>
30-
<Layout.Footer/>
31-
</Layout.Container>
14+
<Layout.Root content={ children }>
15+
<Layout.Container>
16+
<Layout.TopRow/>
17+
<Layout.NavBar/>
18+
<HeaderMobile/>
19+
<Layout.MainArea>
20+
<Layout.SideBar/>
21+
<Layout.MainColumn>
22+
<HeaderAlert/>
23+
<HeaderDesktop/>
24+
<AppErrorBoundary>
25+
<Layout.Content>
26+
{ children }
27+
</Layout.Content>
28+
</AppErrorBoundary>
29+
</Layout.MainColumn>
30+
</Layout.MainArea>
31+
<Layout.Footer/>
32+
</Layout.Container>
33+
</Layout.Root>
3234
);
3335
};
3436

ui/shared/layout/LayoutApp.tsx

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,36 @@ const HEADER_HEIGHT_MOBILE = 56;
1212

1313
const LayoutDefault = ({ children }: Props) => {
1414
return (
15-
<Layout.Container
16-
overflowY="hidden"
17-
height="$100vh"
18-
display="flex"
19-
flexDirection="column"
20-
>
21-
<Layout.TopRow/>
22-
<HeaderMobile hideSearchBar/>
23-
<Layout.MainArea
24-
minH={{
25-
base: `calc(100dvh - ${ TOP_BAR_HEIGHT + HEADER_HEIGHT_MOBILE }px)`,
26-
lg: `calc(100dvh - ${ TOP_BAR_HEIGHT }px)`,
27-
}}
28-
flex={ 1 }
15+
<Layout.Root content={ children }>
16+
<Layout.Container
17+
overflowY="hidden"
18+
height="$100vh"
19+
display="flex"
20+
flexDirection="column"
2921
>
30-
<Layout.MainColumn
31-
paddingTop={{ base: 0, lg: 0 }}
32-
paddingBottom={ 0 }
33-
paddingX={{ base: 4, lg: 6 }}
22+
<Layout.TopRow/>
23+
<HeaderMobile hideSearchBar/>
24+
<Layout.MainArea
25+
minH={{
26+
base: `calc(100dvh - ${ TOP_BAR_HEIGHT + HEADER_HEIGHT_MOBILE }px)`,
27+
lg: `calc(100dvh - ${ TOP_BAR_HEIGHT }px)`,
28+
}}
29+
flex={ 1 }
3430
>
35-
<AppErrorBoundary>
36-
<Layout.Content pt={{ base: 0, lg: 2 }} flexGrow={ 1 }>
37-
{ children }
38-
</Layout.Content>
39-
</AppErrorBoundary>
40-
</Layout.MainColumn>
41-
</Layout.MainArea>
42-
</Layout.Container>
31+
<Layout.MainColumn
32+
paddingTop={{ base: 0, lg: 0 }}
33+
paddingBottom={ 0 }
34+
paddingX={{ base: 4, lg: 6 }}
35+
>
36+
<AppErrorBoundary>
37+
<Layout.Content pt={{ base: 0, lg: 2 }} flexGrow={ 1 }>
38+
{ children }
39+
</Layout.Content>
40+
</AppErrorBoundary>
41+
</Layout.MainColumn>
42+
</Layout.MainArea>
43+
</Layout.Container>
44+
</Layout.Root>
4345
);
4446
};
4547

ui/shared/layout/LayoutError.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@ import * as Layout from './components';
1111

1212
const LayoutError = ({ children }: Props) => {
1313
return (
14-
<Layout.Container>
15-
<Layout.TopRow/>
16-
<Layout.NavBar/>
17-
<HeaderMobile/>
18-
<Layout.MainArea>
19-
<Layout.SideBar/>
20-
<Layout.MainColumn>
21-
<HeaderAlert/>
22-
<HeaderDesktop/>
23-
<AppErrorBoundary>
24-
<main>
25-
{ children }
26-
</main>
27-
</AppErrorBoundary>
28-
</Layout.MainColumn>
29-
</Layout.MainArea>
30-
<Layout.Footer/>
31-
</Layout.Container>
14+
<Layout.Root content={ children }>
15+
<Layout.Container>
16+
<Layout.TopRow/>
17+
<Layout.NavBar/>
18+
<HeaderMobile/>
19+
<Layout.MainArea>
20+
<Layout.SideBar/>
21+
<Layout.MainColumn>
22+
<HeaderAlert/>
23+
<HeaderDesktop/>
24+
<AppErrorBoundary>
25+
<main>
26+
{ children }
27+
</main>
28+
</AppErrorBoundary>
29+
</Layout.MainColumn>
30+
</Layout.MainArea>
31+
<Layout.Footer/>
32+
</Layout.Container>
33+
</Layout.Root>
3234
);
3335
};
3436

ui/shared/layout/LayoutHome.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,25 @@ import * as Layout from './components';
1010

1111
const LayoutHome = ({ children }: Props) => {
1212
return (
13-
<Layout.Container>
14-
<Layout.TopRow/>
15-
<Layout.NavBar/>
16-
<HeaderMobile hideSearchBar/>
17-
<Layout.MainArea>
18-
<Layout.SideBar/>
19-
<Layout.MainColumn
20-
paddingTop={{ base: 3, lg: 6 }}
21-
>
22-
<HeaderAlert/>
23-
<AppErrorBoundary>
24-
{ children }
25-
</AppErrorBoundary>
26-
</Layout.MainColumn>
27-
</Layout.MainArea>
28-
<Layout.Footer/>
29-
</Layout.Container>
13+
<Layout.Root content={ children }>
14+
<Layout.Container>
15+
<Layout.TopRow/>
16+
<Layout.NavBar/>
17+
<HeaderMobile hideSearchBar/>
18+
<Layout.MainArea>
19+
<Layout.SideBar/>
20+
<Layout.MainColumn
21+
paddingTop={{ base: 3, lg: 6 }}
22+
>
23+
<HeaderAlert/>
24+
<AppErrorBoundary>
25+
{ children }
26+
</AppErrorBoundary>
27+
</Layout.MainColumn>
28+
</Layout.MainArea>
29+
<Layout.Footer/>
30+
</Layout.Container>
31+
</Layout.Root>
3032
);
3133
};
3234

0 commit comments

Comments
 (0)