_app.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { SWRConfig } from "swr";
  2. import Script from "next/script";
  3. import type { NextPage } from "next";
  4. import { useRouter } from "next/router";
  5. import type { AppProps } from "next/app";
  6. import App, { AppContext } from "next/app";
  7. import { ReactElement, ReactNode, useEffect, useMemo } from "react";
  8. import { get } from "libs/http";
  9. import { pageview } from "libs/gtag";
  10. import { Context } from "libs/context";
  11. import { GA_TRACKING_ID } from "libs/config";
  12. import Layout from "components/common/Layout";
  13. import getSiteConfig, { SiteConfig } from "libs/getSiteConfig";
  14. import "styles/globals.scss";
  15. import { SeoHead, SeoHeadConfig } from "components/SeoHead";
  16. export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  17. getLayout?: (page: ReactElement) => ReactNode;
  18. };
  19. type AppPropsWithLayout = AppProps & {
  20. Component: NextPageWithLayout;
  21. pageProps: {
  22. fallback?: {
  23. [key: string]: any;
  24. };
  25. siteConfig: SiteConfig;
  26. [key: string]: any;
  27. };
  28. };
  29. const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
  30. const { fallback, genre, siteConfig, ...otherProps } = pageProps;
  31. const router = useRouter();
  32. const seoConfig: SeoHeadConfig = useMemo(() => {
  33. return {
  34. title: siteConfig.title,
  35. description: siteConfig.description,
  36. keywords: siteConfig.keywords,
  37. url: `https://${siteConfig.host}`,
  38. canonical: `https://${siteConfig.host}`,
  39. jsonLd: JSON.stringify(siteConfig.jsonLd),
  40. };
  41. }, [siteConfig.description, siteConfig.host, siteConfig.jsonLd, siteConfig.keywords, siteConfig.title]);
  42. useEffect(() => {
  43. const handleRouteChange = (url: string) => {
  44. pageview(url);
  45. };
  46. router.events.on("routeChangeComplete", handleRouteChange);
  47. router.events.on("hashChangeComplete", handleRouteChange);
  48. return () => {
  49. router.events.off("routeChangeComplete", handleRouteChange);
  50. router.events.off("hashChangeComplete", handleRouteChange);
  51. };
  52. }, [router.events]);
  53. return (
  54. <>
  55. <SeoHead seoConfig={seoConfig}>
  56. <meta charSet="utf-8" />
  57. <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
  58. <meta
  59. name="viewport"
  60. content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
  61. />
  62. <title>{siteConfig.title}</title>
  63. <meta name="description" content={siteConfig.description} />
  64. <meta name="keywords" content={siteConfig.keywords} />
  65. <meta
  66. property="og:url"
  67. key="og:url"
  68. content={`https://${siteConfig.host}`}
  69. />
  70. <meta
  71. property="og:site_name"
  72. key="og:site_name"
  73. content={siteConfig.siteName}
  74. />
  75. <meta property="og:title" content={siteConfig.title} key="og:title" />
  76. <meta
  77. property="og:description"
  78. key="og:description"
  79. content={siteConfig.description}
  80. />
  81. <meta
  82. property="og:image"
  83. key="og:image"
  84. content={siteConfig.touchIcon}
  85. />
  86. <meta
  87. name="twitter:title"
  88. key="twitter:title"
  89. content={siteConfig.title}
  90. />
  91. <meta
  92. name="twitter:description"
  93. key="twitter:description"
  94. content={siteConfig.description}
  95. />
  96. <meta name="twitter:card" key="twitter:card" content="summary" />
  97. <meta
  98. name="twitter:image"
  99. key="twitter:image"
  100. content={siteConfig.touchIcon}
  101. />
  102. <link
  103. rel="canonical"
  104. key="canonical"
  105. href={`https://${siteConfig.host}`}
  106. />
  107. <link rel="manifest" href="/manifest.json" />
  108. <link rel="icon" href="/logo.svg" type="image/svg+xml" />
  109. <link rel="icon" href="/favicon.ico" type="image/x-icon" />
  110. <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
  111. <link rel="mask-icon" href="/logo.svg" color="#000000" />
  112. <link
  113. href="/favicon-16x16.png"
  114. rel="icon"
  115. type="image/png"
  116. sizes="16x16"
  117. />
  118. <link
  119. href="/favicon-32x32.png"
  120. rel="icon"
  121. type="image/png"
  122. sizes="32x32"
  123. />
  124. <link rel="apple-touch-icon" href={siteConfig.touchIcon} />
  125. <meta name="apple-mobile-web-app-title" content={siteConfig.siteName} />
  126. <meta name="application-name" content={siteConfig.siteName} />
  127. <meta
  128. name="theme-color"
  129. content="#ffffff"
  130. media="(prefers-color-scheme: light)"
  131. data-react-helmet="true"
  132. />
  133. <meta
  134. name="theme-color"
  135. content="#111827"
  136. media="(prefers-color-scheme: dark)"
  137. data-react-helmet="true"
  138. />
  139. <meta data-rh="true" name="theme-color" content="#111827" />
  140. <meta name="msapplication-TileColor" content="#5b5b5b" />
  141. <meta name="msapplication-TileImage" content={siteConfig.touchIcon} />
  142. <meta name="msapplication-tooltip" content={siteConfig.title} />
  143. <script
  144. type="application/ld+json"
  145. key="application/ld+json"
  146. dangerouslySetInnerHTML={{
  147. __html: JSON.stringify(siteConfig.jsonLd),
  148. }}
  149. />
  150. </SeoHead>
  151. <Script
  152. strategy="afterInteractive"
  153. src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
  154. />
  155. <Context.Provider value={{ genre, siteConfig }}>
  156. <SWRConfig value={{ fallback, revalidateIfStale: false }}>
  157. {Component.getLayout && !pageProps.statusCode ? (
  158. Component.getLayout(<Component {...otherProps} />)
  159. ) : (
  160. <Layout>
  161. <Component {...otherProps} />
  162. </Layout>
  163. )}
  164. </SWRConfig>
  165. </Context.Provider>
  166. </>
  167. );
  168. };
  169. MyApp.getInitialProps = async function (context: AppContext) {
  170. App.getInitialProps(context);
  171. const { data } = await get<GenreItem[]>("/api/genre/list");
  172. return {
  173. pageProps: {
  174. genre: data,
  175. fallback: { "/api/genre/list": data },
  176. siteConfig: getSiteConfig(context.ctx.req?.headers.host),
  177. },
  178. };
  179. };
  180. export default MyApp;