Use built-in Next layout support + lazy load

Instead of doing manual layout switching use the Nextjs nested layout
support. Also add some additional lazy loading of components. This is to
work on performance score re: #2167.
This commit is contained in:
Gabe Kangas 2023-01-09 01:06:39 -08:00
parent 15747f86dd
commit c05a20a460
No known key found for this signature in database
GPG key ID: 4345B2060657F330
8 changed files with 108 additions and 83 deletions

View file

@ -1,37 +0,0 @@
/* eslint-disable @next/next/no-css-tags */
import { AppProps } from 'next/app';
import { FC } from 'react';
import ServerStatusProvider from '../../utils/server-status-context';
import AlertMessageProvider from '../../utils/alert-message-context';
import { MainLayout } from '../MainLayout';
/*
NOTE: A bunch of compiled css is loaded here for the Admin UI.
These are old stylesheets that were converted from sass and should not be
edited or maintained. Instead we are using css modules everywhere. So if you
need to change a style rewrite the css file as a css module and import it
into the component that needs it, removing it from this global list.
*/
export const AdminLayout: FC<AppProps> = ({ Component, pageProps }) => (
<>
<link rel="stylesheet" href="/styles/admin/main-layout.css" />
<link rel="stylesheet" href="/styles/admin/form-textfields.css" />
<link rel="stylesheet" href="/styles/admin/config-socialhandles.css" />
<link rel="stylesheet" href="/styles/admin/config-storage.css" />
<link rel="stylesheet" href="/styles/admin/config-edit-string-tags.css" />
<link rel="stylesheet" href="/styles/admin/config-video-variants.css" />
<link rel="stylesheet" href="/styles/admin/config-public-details.css" />
<link rel="stylesheet" href="/styles/admin/home.css" />
<link rel="stylesheet" href="/styles/admin/chat.css" />
<link rel="stylesheet" href="/styles/admin/pages.css" />
<link rel="stylesheet" href="/styles/admin/offline-notice.css" />
<ServerStatusProvider>
<AlertMessageProvider>
<MainLayout>
<Component {...pageProps} />
</MainLayout>
</AlertMessageProvider>
</ServerStatusProvider>
</>
);

View file

@ -5,23 +5,37 @@ import { useRecoilValue } from 'recoil';
import Head from 'next/head'; import Head from 'next/head';
import { FC, useEffect, useRef } from 'react'; import { FC, useEffect, useRef } from 'react';
import { useLockBodyScroll } from 'react-use'; import { useLockBodyScroll } from 'react-use';
import dynamic from 'next/dynamic';
import { import {
ClientConfigStore, ClientConfigStore,
isChatAvailableSelector, isChatAvailableSelector,
clientConfigStateAtom, clientConfigStateAtom,
fatalErrorStateAtom, fatalErrorStateAtom,
} from '../stores/ClientConfigStore'; } from '../stores/ClientConfigStore';
import { Content } from '../ui/Content/Content';
import { Header } from '../ui/Header/Header'; import { Header } from '../ui/Header/Header';
import { ClientConfig } from '../../interfaces/client-config.model'; import { ClientConfig } from '../../interfaces/client-config.model';
import { DisplayableError } from '../../types/displayable-error'; import { DisplayableError } from '../../types/displayable-error';
import { FatalErrorStateModal } from '../modals/FatalErrorStateModal/FatalErrorStateModal';
import setupNoLinkReferrer from '../../utils/no-link-referrer'; import setupNoLinkReferrer from '../../utils/no-link-referrer';
import { TitleNotifier } from '../TitleNotifier/TitleNotifier'; import { TitleNotifier } from '../TitleNotifier/TitleNotifier';
import { ServerRenderedHydration } from '../ServerRendered/ServerRenderedHydration'; import { ServerRenderedHydration } from '../ServerRendered/ServerRenderedHydration';
import { PushNotificationServiceWorker } from '../workers/PushNotificationServiceWorker/PushNotificationServiceWorker';
import { Content } from '../ui/Content/Content';
import { Theme } from '../theme/Theme'; import { Theme } from '../theme/Theme';
// Lazy loaded components
const FatalErrorStateModal = dynamic(
() =>
import('../modals/FatalErrorStateModal/FatalErrorStateModal').then(
mod => mod.FatalErrorStateModal,
),
{
loading: () => <div>Loading...</div>,
ssr: false,
},
);
export const Main: FC = () => { export const Main: FC = () => {
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom); const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const { name, title, customStyles } = clientConfig; const { name, title, customStyles } = clientConfig;
@ -111,6 +125,7 @@ export const Main: FC = () => {
)} )}
<ClientConfigStore /> <ClientConfigStore />
<PushNotificationServiceWorker />
<TitleNotifier name={name} /> <TitleNotifier name={name} />
<Theme /> <Theme />
<Layout ref={layoutRef} style={{ minHeight: '100vh' }}> <Layout ref={layoutRef} style={{ minHeight: '100vh' }}>

View file

@ -1,8 +0,0 @@
import { AppProps } from 'next/app';
import { FC } from 'react';
export const SimpleLayout: FC<AppProps> = ({ Component, pageProps }) => (
<div>
<Component {...pageProps} />
</div>
);

View file

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

View file

@ -0,0 +1,27 @@
/* eslint-disable react/no-danger */
import { FC, useEffect } from 'react';
export const PushNotificationServiceWorker: FC = () => {
const add = () => {
navigator.serviceWorker.register('/serviceWorker.js').then(
registration => {
console.debug('Service Worker registration successful with scope: ', registration.scope);
},
err => {
console.error('Service Worker registration failed: ', err);
},
);
};
useEffect(() => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', add);
}
return () => {
window.removeEventListener('load', add);
};
}, []);
return null;
};

View file

@ -11,41 +11,25 @@ import '../styles/ant-overrides.scss';
import '../components/video/VideoJS/VideoJS.scss'; import '../components/video/VideoJS/VideoJS.scss';
import { AppProps } from 'next/app'; import { AppProps } from 'next/app';
import { Router, useRouter } from 'next/router'; import { ReactElement, ReactNode } from 'react';
import { NextPage } from 'next';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { useEffect } from 'react';
import { AdminLayout } from '../components/layouts/AdminLayout';
import { SimpleLayout } from '../components/layouts/SimpleLayout';
const App = ({ Component, pageProps }: AppProps) => { export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
useEffect(() => { getLayout?: (page: ReactElement) => ReactNode;
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/serviceWorker.js').then(
registration => {
console.debug(
'Service Worker registration successful with scope: ',
registration.scope,
);
},
err => {
console.error('Service Worker registration failed: ', err);
},
);
});
}
}, []);
const router = useRouter() as Router;
if (router.pathname.startsWith('/admin')) {
return <AdminLayout pageProps={pageProps} Component={Component} router={router} />;
}
return (
<RecoilRoot>
<SimpleLayout pageProps={pageProps} Component={Component} router={router} />
</RecoilRoot>
);
}; };
export default App; type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
export default function App({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? (page => page);
return getLayout(
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>,
);
}

View file

@ -1,8 +1,9 @@
import React, { useState, useEffect, useContext } from 'react'; /* eslint-disable @next/next/no-css-tags */
import React, { useState, useEffect, useContext, ReactElement } from 'react';
import { Skeleton, Card, Statistic, Row, Col } from 'antd'; import { Skeleton, Card, Statistic, Row, Col } from 'antd';
import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons'; import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons';
import { formatDistanceToNow, formatRelative } from 'date-fns'; import { formatDistanceToNow, formatRelative } from 'date-fns';
import { ServerStatusContext } from '../../utils/server-status-context'; import ServerStatusProvider, { ServerStatusContext } from '../../utils/server-status-context';
import { LogTable } from '../../components/LogTable'; import { LogTable } from '../../components/LogTable';
import { Offline } from '../../components/Offline'; import { Offline } from '../../components/Offline';
import { StreamHealthOverview } from '../../components/StreamHealthOverview'; import { StreamHealthOverview } from '../../components/StreamHealthOverview';
@ -11,6 +12,9 @@ import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis';
import { formatIPAddress, isEmptyObject } from '../../utils/format'; import { formatIPAddress, isEmptyObject } from '../../utils/format';
import { NewsFeed } from '../../components/NewsFeed'; import { NewsFeed } from '../../components/NewsFeed';
import AlertMessageProvider from '../../utils/alert-message-context';
import { MainLayout } from '../../components/MainLayout';
function streamDetailsFormatter(streamDetails) { function streamDetailsFormatter(streamDetails) {
return ( return (
<ul className="statistics-list"> <ul className="statistics-list">
@ -178,3 +182,27 @@ export default function Home() {
</div> </div>
); );
} }
Home.getLayout = function getLayout(page: ReactElement) {
return (
<>
<link rel="stylesheet" href="/styles/admin/main-layout.css" />
<link rel="stylesheet" href="/styles/admin/form-textfields.css" />
<link rel="stylesheet" href="/styles/admin/config-socialhandles.css" />
<link rel="stylesheet" href="/styles/admin/config-storage.css" />
<link rel="stylesheet" href="/styles/admin/config-edit-string-tags.css" />
<link rel="stylesheet" href="/styles/admin/config-video-variants.css" />
<link rel="stylesheet" href="/styles/admin/config-public-details.css" />
<link rel="stylesheet" href="/styles/admin/home.css" />
<link rel="stylesheet" href="/styles/admin/chat.css" />
<link rel="stylesheet" href="/styles/admin/pages.css" />
<link rel="stylesheet" href="/styles/admin/offline-notice.css" />
<ServerStatusProvider>
<AlertMessageProvider>
<MainLayout>{page}</MainLayout>
</AlertMessageProvider>
</ServerStatusProvider>
</>
);
};

View file

@ -1,5 +1,9 @@
import { ReactElement } from 'react';
import { Main } from '../components/layouts/Main'; import { Main } from '../components/layouts/Main';
export default function Home() { export default function Home() {
return <Main />; return <Main />;
} }
Home.getLayout = function getLayout(page: ReactElement) {
return page;
};