feat(frontend): server side rendering to improve performance

This commit is contained in:
Elias Schneider
2023-02-07 10:21:25 +01:00
parent 82f204e8a9
commit 38de022215
14 changed files with 137 additions and 71 deletions

View File

@@ -2,12 +2,14 @@ import {
ColorScheme,
ColorSchemeProvider,
Container,
LoadingOverlay,
MantineProvider,
} from "@mantine/core";
import { useColorScheme } from "@mantine/hooks";
import { ModalsProvider } from "@mantine/modals";
import { NotificationsProvider } from "@mantine/notifications";
import axios from "axios";
import { getCookie, setCookie } from "cookies-next";
import { GetServerSidePropsContext } from "next";
import type { AppProps } from "next/app";
import { useEffect, useState } from "react";
import Header from "../components/navBar/NavBar";
@@ -21,38 +23,38 @@ import GlobalStyle from "../styles/global.style";
import globalStyle from "../styles/mantine.style";
import Config from "../types/config.type";
import { CurrentUser } from "../types/user.type";
import { GlobalLoadingContext } from "../utils/loading.util";
function App({ Component, pageProps }: AppProps) {
const systemTheme = useColorScheme();
const systemTheme = useColorScheme(pageProps.colorScheme);
const [colorScheme, setColorScheme] = useState<ColorScheme>(systemTheme);
const preferences = usePreferences();
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<CurrentUser | null>(null);
const [configVariables, setConfigVariables] = useState<Config[] | null>(null);
const getInitalData = async () => {
setIsLoading(true);
setConfigVariables(await configService.list());
await authService.refreshAccessToken();
setUser(await userService.getCurrentUser());
setIsLoading(false);
};
const [user, setUser] = useState<CurrentUser | null>(pageProps.user);
const [configVariables, setConfigVariables] = useState<Config[]>(
pageProps.configVariables
);
useEffect(() => {
setInterval(async () => await authService.refreshAccessToken(), 30 * 1000);
getInitalData();
}, []);
useEffect(() => {
setColorScheme(
const colorScheme =
preferences.get("colorScheme") == "system"
? systemTheme
: preferences.get("colorScheme")
);
: preferences.get("colorScheme");
toggleColorScheme(colorScheme);
}, [systemTheme]);
const toggleColorScheme = (value: ColorScheme) => {
setColorScheme(value ?? "light");
setCookie("mantine-color-scheme", value ?? "light", {
sameSite: "lax",
});
};
return (
<MantineProvider
withGlobalStyles
@@ -61,26 +63,35 @@ function App({ Component, pageProps }: AppProps) {
>
<ColorSchemeProvider
colorScheme={colorScheme}
toggleColorScheme={(value) => setColorScheme(value ?? "light")}
toggleColorScheme={toggleColorScheme}
>
<GlobalStyle />
<NotificationsProvider>
<ModalsProvider>
<GlobalLoadingContext.Provider value={{ isLoading, setIsLoading }}>
{isLoading ? (
<LoadingOverlay visible overlayOpacity={1} />
) : (
<ConfigContext.Provider value={configVariables}>
<UserContext.Provider value={{ user, setUser }}>
<LoadingOverlay visible={isLoading} overlayOpacity={1} />
<Header />
<Container>
<Component {...pageProps} />
</Container>
</UserContext.Provider>
</ConfigContext.Provider>
)}
</GlobalLoadingContext.Provider>
<ConfigContext.Provider
value={{
configVariables,
refresh: async () => {
setConfigVariables(await configService.list());
},
}}
>
<UserContext.Provider
value={{
user,
refreshUser: async () => {
const user = await userService.getCurrentUser();
setUser(user);
return user;
},
}}
>
<Header />
<Container>
<Component {...pageProps} />
</Container>
</UserContext.Provider>
</ConfigContext.Provider>
</ModalsProvider>
</NotificationsProvider>
</ColorSchemeProvider>
@@ -88,4 +99,33 @@ function App({ Component, pageProps }: AppProps) {
);
}
// Fetch user and config variables on server side when the first request is made
// These will get passed as a page prop to the App component and stored in the contexts
App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
let pageProps: {
user?: CurrentUser;
configVariables?: Config[];
colorScheme: ColorScheme;
} = {
colorScheme:
(getCookie("mantine-color-scheme", ctx) as ColorScheme) ?? "light",
};
if (ctx.req) {
const cookieHeader = ctx.req.headers.cookie;
pageProps.user = await axios(`http://localhost:8080/api/users/me`, {
headers: { cookie: cookieHeader },
})
.then((res) => res.data)
.catch(() => null);
pageProps.configVariables = (
await axios(`http://localhost:8080/api/configs`)
).data;
}
return { pageProps };
};
export default App;

View File

@@ -24,7 +24,7 @@ import userService from "../../services/user.service";
import toast from "../../utils/toast.util";
const Account = () => {
const { user, setUser } = useUser();
const { user, refreshUser } = useUser();
const modals = useModals();
const accountForm = useForm({
@@ -81,8 +81,6 @@ const Account = () => {
),
});
const refreshUser = async () => setUser(await userService.getCurrentUser());
return (
<>
<Meta title="My account" />

View File

@@ -4,7 +4,6 @@ import {
Button,
Center,
Group,
LoadingOverlay,
Space,
Stack,
Table,
@@ -18,6 +17,7 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import { TbLink, TbTrash } from "react-icons/tb";
import showShareLinkModal from "../../components/account/showShareLinkModal";
import CenterLoader from "../../components/core/CenterLoader";
import Meta from "../../components/Meta";
import useConfig from "../../hooks/config.hook";
import shareService from "../../services/share.service";
@@ -35,7 +35,8 @@ const MyShares = () => {
shareService.getMyShares().then((shares) => setShares(shares));
}, []);
if (!shares) return <LoadingOverlay visible />;
if (!shares) return <CenterLoader />;
return (
<>
<Meta title="My shares" />

View File

@@ -1,26 +1,41 @@
import { LoadingOverlay } from "@mantine/core";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import SignInForm from "../../components/auth/SignInForm";
import Meta from "../../components/Meta";
import useUser from "../../hooks/user.hook";
const SignIn = () => {
const { user } = useUser();
export function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: { redirectPath: context.query.redirect ?? null },
};
}
const SignIn = ({ redirectPath }: { redirectPath?: string }) => {
const { refreshUser } = useUser();
const router = useRouter();
const redirectPath = (router.query.redirect as string) ?? "/upload";
const [isLoading, setIsLoading] = useState(redirectPath ? true : false);
// If the access token is expired, the middleware redirects to this page.
// If the refresh token is still valid, the user will be redirected to the home page.
if (user) {
router.replace(redirectPath);
return <LoadingOverlay overlayOpacity={1} visible />;
}
// If the refresh token is still valid, the user will be redirected to the last page.
useEffect(() => {
refreshUser().then((user) => {
if (user) {
router.replace(redirectPath ?? "/upload");
} else {
setIsLoading(false);
}
});
}, []);
if (isLoading) return <LoadingOverlay overlayOpacity={1} visible />;
return (
<>
<Meta title="Sign In" />
<SignInForm redirectPath={redirectPath} />
<SignInForm redirectPath={redirectPath ?? "/upload"} />
</>
);
};