Browse Source

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.
pull/2562/head
Gabe Kangas 3 years ago
parent
commit
c05a20a460
No known key found for this signature in database
GPG Key ID: 4345B2060657F330
  1. 37
      web/components/layouts/AdminLayout.tsx
  2. 19
      web/components/layouts/Main.tsx
  3. 8
      web/components/layouts/SimpleLayout.tsx
  4. 12
      web/components/workers/PushNotificationServiceWorker/.editorconfig
  5. 27
      web/components/workers/PushNotificationServiceWorker/PushNotificationServiceWorker.tsx
  6. 50
      web/pages/_app.tsx
  7. 32
      web/pages/admin/index.tsx
  8. 4
      web/pages/index.tsx

37
web/components/layouts/AdminLayout.tsx

@ -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>
</>
);

19
web/components/layouts/Main.tsx

@ -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' }}>

8
web/components/layouts/SimpleLayout.tsx

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

12
web/components/workers/PushNotificationServiceWorker/.editorconfig

@ -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

27
web/components/workers/PushNotificationServiceWorker/PushNotificationServiceWorker.tsx

@ -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;
};

50
web/pages/_app.tsx

@ -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; type AppPropsWithLayout = AppProps & {
if (router.pathname.startsWith('/admin')) { Component: NextPageWithLayout;
return <AdminLayout pageProps={pageProps} Component={Component} router={router} />;
}
return (
<RecoilRoot>
<SimpleLayout pageProps={pageProps} Component={Component} router={router} />
</RecoilRoot>
);
}; };
export default App; 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>,
);
}

32
web/pages/admin/index.tsx

@ -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>
</>
);
};

4
web/pages/index.tsx

@ -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;
};

Loading…
Cancel
Save