mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
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
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import type { GrowthBook } from '@growthbook/growthbook-react';
|
||||
import React from 'react';
|
||||
|
||||
import { SECOND } from 'toolkit/utils/consts';
|
||||
|
||||
import { initGrowthBook } from './init';
|
||||
|
||||
export default function useLoadFeatures(uuid: string) {
|
||||
const growthBook = initGrowthBook(uuid);
|
||||
export default function useLoadFeatures(growthBook: GrowthBook | undefined) {
|
||||
React.useEffect(() => {
|
||||
growthBook?.setAttributes({
|
||||
if (!growthBook) {
|
||||
return;
|
||||
}
|
||||
|
||||
growthBook.setAttributes({
|
||||
...growthBook.getAttributes(),
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
language: window.navigator.language,
|
||||
});
|
||||
|
||||
growthBook?.loadFeatures({ timeout: SECOND });
|
||||
growthBook.loadFeatures({ timeout: SECOND });
|
||||
}, [ growthBook ]);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ const moduleExports = {
|
||||
headers,
|
||||
output: 'standalone',
|
||||
productionBrowserSourceMaps: true,
|
||||
serverExternalPackages: ["@opentelemetry/sdk-node", "@opentelemetry/auto-instrumentations-node"],
|
||||
experimental: {
|
||||
staleTimes: {
|
||||
dynamic: 30,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import type { Route } from 'nextjs-routes';
|
||||
import type { Props as PageProps } from 'nextjs/getServerSideProps';
|
||||
|
||||
import config from 'configs/app';
|
||||
import * as metadata from 'lib/metadata';
|
||||
|
||||
interface Props<Pathname extends Route['pathname']> {
|
||||
pathname: Pathname;
|
||||
query?: PageProps<Pathname>['query'];
|
||||
apiData?: PageProps<Pathname>['apiData'];
|
||||
}
|
||||
|
||||
const PageMetadata = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
|
||||
const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData);
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>{ title }</title>
|
||||
<meta name="description" content={ description }/>
|
||||
{ canonical && <link rel="canonical" href={ canonical }/> }
|
||||
|
||||
{ /* OG TAGS */ }
|
||||
<meta property="og:title" content={ opengraph.title }/>
|
||||
{ opengraph.description && <meta property="og:description" content={ opengraph.description }/> }
|
||||
<meta property="og:image" content={ opengraph.imageUrl }/>
|
||||
<meta property="og:type" content="website"/>
|
||||
|
||||
{ /* Twitter Meta Tags */ }
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta property="twitter:domain" content={ config.app.host }/>
|
||||
<meta name="twitter:title" content={ opengraph.title }/>
|
||||
{ opengraph.description && <meta name="twitter:description" content={ opengraph.description }/> }
|
||||
<meta property="twitter:image" content={ opengraph.imageUrl }/>
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageMetadata;
|
||||
+7
-23
@@ -1,13 +1,13 @@
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import type { Route } from 'nextjs-routes';
|
||||
import type { Props as PageProps } from 'nextjs/getServerSideProps';
|
||||
import PageMetadata from 'nextjs/PageMetadata';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
|
||||
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
|
||||
import * as metadata from 'lib/metadata';
|
||||
import useIsMounted from 'lib/hooks/useIsMounted';
|
||||
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
interface Props<Pathname extends Route['pathname']> {
|
||||
@@ -18,35 +18,19 @@ interface Props<Pathname extends Route['pathname']> {
|
||||
}
|
||||
|
||||
const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
|
||||
const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData);
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
useGetCsrfToken();
|
||||
useAdblockDetect();
|
||||
useNotifyOnNavigation();
|
||||
|
||||
const isMixpanelInited = mixpanel.useInit();
|
||||
mixpanel.useLogPageView(isMixpanelInited);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{ title }</title>
|
||||
<meta name="description" content={ description }/>
|
||||
{ canonical && <link rel="canonical" href={ canonical }/> }
|
||||
|
||||
{ /* OG TAGS */ }
|
||||
<meta property="og:title" content={ opengraph.title }/>
|
||||
{ opengraph.description && <meta property="og:description" content={ opengraph.description }/> }
|
||||
<meta property="og:image" content={ opengraph.imageUrl }/>
|
||||
<meta property="og:type" content="website"/>
|
||||
|
||||
{ /* Twitter Meta Tags */ }
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta property="twitter:domain" content={ config.app.host }/>
|
||||
<meta name="twitter:title" content={ opengraph.title }/>
|
||||
{ opengraph.description && <meta name="twitter:description" content={ opengraph.description }/> }
|
||||
<meta property="twitter:image" content={ opengraph.imageUrl }/>
|
||||
</Head>
|
||||
{ props.children }
|
||||
<PageMetadata pathname={ props.pathname } query={ props.query } apiData={ props.apiData }/>
|
||||
{ isMounted ? props.children : null }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+18
-24
@@ -16,7 +16,6 @@ import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
|
||||
import { SettingsContextProvider } from 'lib/contexts/settings';
|
||||
import { initGrowthBook } from 'lib/growthbook/init';
|
||||
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
|
||||
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
|
||||
import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/rollbar';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import { Provider as ChakraProvider } from 'toolkit/chakra/provider';
|
||||
@@ -49,26 +48,28 @@ const ERROR_SCREEN_STYLES: HTMLChakraProps<'div'> = {
|
||||
};
|
||||
|
||||
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
// to avoid hydration mismatch between server and client
|
||||
// we have to render the app only on client (when it is mounted)
|
||||
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
|
||||
const [ mounted, setMounted ] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useLoadFeatures(pageProps.uuid);
|
||||
useNotifyOnNavigation();
|
||||
|
||||
const growthBook = initGrowthBook(pageProps.uuid);
|
||||
useLoadFeatures(growthBook);
|
||||
|
||||
const queryClient = useQueryClientConfig();
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
const content = (() => {
|
||||
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
|
||||
|
||||
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
|
||||
return (
|
||||
<>
|
||||
{ getLayout(<Component { ...pageProps }/>) }
|
||||
<Toaster/>
|
||||
{ config.features.rewards.isEnabled && (
|
||||
<>
|
||||
<RewardsLoginModal/>
|
||||
<RewardsActivityTracker/>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
<ChakraProvider>
|
||||
@@ -86,14 +87,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<RewardsContextProvider>
|
||||
<MarketplaceContextProvider>
|
||||
<SettingsContextProvider>
|
||||
{ getLayout(<Component { ...pageProps }/>) }
|
||||
<Toaster/>
|
||||
{ config.features.rewards.isEnabled && (
|
||||
<>
|
||||
<RewardsLoginModal/>
|
||||
<RewardsActivityTracker/>
|
||||
</>
|
||||
) }
|
||||
{ content }
|
||||
</SettingsContextProvider>
|
||||
</MarketplaceContextProvider>
|
||||
</RewardsContextProvider>
|
||||
|
||||
+20
-18
@@ -11,24 +11,26 @@ import * as Layout from './components';
|
||||
|
||||
const LayoutDefault = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn>
|
||||
<HeaderAlert/>
|
||||
<HeaderDesktop/>
|
||||
<AppErrorBoundary>
|
||||
<Layout.Content>
|
||||
{ children }
|
||||
</Layout.Content>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
<Layout.Root content={ children }>
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn>
|
||||
<HeaderAlert/>
|
||||
<HeaderDesktop/>
|
||||
<AppErrorBoundary>
|
||||
<Layout.Content>
|
||||
{ children }
|
||||
</Layout.Content>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
</Layout.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,34 +12,36 @@ const HEADER_HEIGHT_MOBILE = 56;
|
||||
|
||||
const LayoutDefault = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container
|
||||
overflowY="hidden"
|
||||
height="$100vh"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Layout.TopRow/>
|
||||
<HeaderMobile hideSearchBar/>
|
||||
<Layout.MainArea
|
||||
minH={{
|
||||
base: `calc(100dvh - ${ TOP_BAR_HEIGHT + HEADER_HEIGHT_MOBILE }px)`,
|
||||
lg: `calc(100dvh - ${ TOP_BAR_HEIGHT }px)`,
|
||||
}}
|
||||
flex={ 1 }
|
||||
<Layout.Root content={ children }>
|
||||
<Layout.Container
|
||||
overflowY="hidden"
|
||||
height="$100vh"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Layout.MainColumn
|
||||
paddingTop={{ base: 0, lg: 0 }}
|
||||
paddingBottom={ 0 }
|
||||
paddingX={{ base: 4, lg: 6 }}
|
||||
<Layout.TopRow/>
|
||||
<HeaderMobile hideSearchBar/>
|
||||
<Layout.MainArea
|
||||
minH={{
|
||||
base: `calc(100dvh - ${ TOP_BAR_HEIGHT + HEADER_HEIGHT_MOBILE }px)`,
|
||||
lg: `calc(100dvh - ${ TOP_BAR_HEIGHT }px)`,
|
||||
}}
|
||||
flex={ 1 }
|
||||
>
|
||||
<AppErrorBoundary>
|
||||
<Layout.Content pt={{ base: 0, lg: 2 }} flexGrow={ 1 }>
|
||||
{ children }
|
||||
</Layout.Content>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
</Layout.Container>
|
||||
<Layout.MainColumn
|
||||
paddingTop={{ base: 0, lg: 0 }}
|
||||
paddingBottom={ 0 }
|
||||
paddingX={{ base: 4, lg: 6 }}
|
||||
>
|
||||
<AppErrorBoundary>
|
||||
<Layout.Content pt={{ base: 0, lg: 2 }} flexGrow={ 1 }>
|
||||
{ children }
|
||||
</Layout.Content>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
</Layout.Container>
|
||||
</Layout.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -11,24 +11,26 @@ import * as Layout from './components';
|
||||
|
||||
const LayoutError = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn>
|
||||
<HeaderAlert/>
|
||||
<HeaderDesktop/>
|
||||
<AppErrorBoundary>
|
||||
<main>
|
||||
{ children }
|
||||
</main>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
<Layout.Root content={ children }>
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn>
|
||||
<HeaderAlert/>
|
||||
<HeaderDesktop/>
|
||||
<AppErrorBoundary>
|
||||
<main>
|
||||
{ children }
|
||||
</main>
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
</Layout.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,23 +10,25 @@ import * as Layout from './components';
|
||||
|
||||
const LayoutHome = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile hideSearchBar/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn
|
||||
paddingTop={{ base: 3, lg: 6 }}
|
||||
>
|
||||
<HeaderAlert/>
|
||||
<AppErrorBoundary>
|
||||
{ children }
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
<Layout.Root content={ children }>
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile hideSearchBar/>
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
<Layout.MainColumn
|
||||
paddingTop={{ base: 3, lg: 6 }}
|
||||
>
|
||||
<HeaderAlert/>
|
||||
<AppErrorBoundary>
|
||||
{ children }
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
</Layout.Container>
|
||||
</Layout.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ import * as Layout from './components';
|
||||
const LayoutSearchResults = ({ children }: Props) => {
|
||||
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
{ children }
|
||||
</Layout.Container>
|
||||
<Layout.Root content={ children }>
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
{ children }
|
||||
</Layout.Container>
|
||||
</Layout.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import useIsMounted from 'lib/hooks/useIsMounted';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
||||
const Root = ({ children, content }: Props) => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
if (!isMounted) {
|
||||
return content;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default React.memo(Root);
|
||||
@@ -6,9 +6,11 @@ import Content from './Content';
|
||||
import MainArea from './MainArea';
|
||||
import MainColumn from './MainColumn';
|
||||
import NavBar from './NavBar';
|
||||
import Root from './Root';
|
||||
import SideBar from './SideBar';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Container,
|
||||
Content,
|
||||
MainArea,
|
||||
@@ -19,7 +21,8 @@ export {
|
||||
TopRow,
|
||||
};
|
||||
|
||||
// Container
|
||||
// Root
|
||||
// Container
|
||||
// TopRow
|
||||
// MainArea
|
||||
// SideBar
|
||||
|
||||
Reference in New Issue
Block a user