_app.tsx 5.1 KB

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