Skip to content

Commit ac4ca51

Browse files
committed
perf(nextjs): Add SSR to project and executions page and imrpove SSR on results page
1 parent d84fc6f commit ac4ca51

File tree

5 files changed

+109
-58
lines changed

5 files changed

+109
-58
lines changed

pages/_app.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { QueryClient, QueryClientProvider } from 'react-query';
33
import { Hydrate } from 'react-query/hydration';
44

@@ -32,10 +32,7 @@ export default function App({ Component, pageProps }: AppProps) {
3232
// ! Need resolutions in monorepo package.json for <Theme></Theme> to not cause "invalid hooks usage" error
3333

3434
// React-Query
35-
const queryClientRef = useRef<QueryClient | null>(null);
36-
if (!queryClientRef.current) {
37-
queryClientRef.current = new QueryClient();
38-
}
35+
const [queryClient] = useState(() => new QueryClient());
3936

4037
// Material UI for SSR
4138
useEffect(() => {
@@ -54,7 +51,7 @@ export default function App({ Component, pageProps }: AppProps) {
5451
<ColorSchemeProvider>
5552
<ThemeProviders>
5653
<UserProvider>
57-
<QueryClientProvider client={queryClientRef.current}>
54+
<QueryClientProvider client={queryClient}>
5855
<Hydrate state={pageProps.dehydratedState}>
5956
<SnackbarProvider>
6057
<SelectedFilesProvider>

pages/executions.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import { useMemo } from 'react';
22
import { useState } from 'react';
3-
4-
import { useGetApplications } from '@squonk/data-manager-client/application';
5-
import { useGetJobs } from '@squonk/data-manager-client/job';
6-
7-
import { withPageAuthRequired } from '@auth0/nextjs-auth0';
3+
import { QueryClient } from 'react-query';
4+
import { dehydrate } from 'react-query/hydration';
5+
6+
import {
7+
getApplications,
8+
getGetApplicationsQueryKey,
9+
useGetApplications,
10+
} from '@squonk/data-manager-client/application';
11+
import { getGetJobsQueryKey, getJobs, useGetJobs } from '@squonk/data-manager-client/job';
12+
import { getGetProjectsQueryKey, getProjects } from '@squonk/data-manager-client/project';
13+
14+
import { getAccessToken, withPageAuthRequired } from '@auth0/nextjs-auth0';
815
import { css } from '@emotion/react';
916
import { Container, Grid, MenuItem, TextField, useTheme } from '@material-ui/core';
1017
import { Alert } from '@material-ui/lab';
18+
import type { GetServerSideProps } from 'next';
1119
import Head from 'next/head';
1220

1321
import { CenterLoader } from '../components/CenterLoader';
@@ -18,6 +26,42 @@ import { SearchTextField } from '../components/SearchTextField';
1826
import { useCurrentProject } from '../hooks/projectHooks';
1927
import { RoleRequired } from '../utils/RoleRequired';
2028
import { search } from '../utils/search';
29+
import { options } from '../utils/ssrQueryOptions';
30+
31+
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
32+
const queryClient = new QueryClient();
33+
34+
try {
35+
const { accessToken } = await getAccessToken(req, res);
36+
37+
const projectId = query.project as string | undefined;
38+
39+
if (projectId && accessToken) {
40+
// Prefetch some data
41+
const queries = [
42+
queryClient.prefetchQuery(getGetProjectsQueryKey(), () =>
43+
getProjects(options(accessToken)),
44+
),
45+
queryClient.prefetchQuery(getGetApplicationsQueryKey(), () =>
46+
getApplications(options(accessToken)),
47+
),
48+
queryClient.prefetchQuery(getGetJobsQueryKey(), () => getJobs(options(accessToken))),
49+
];
50+
51+
// Make the queries in parallel
52+
await Promise.allSettled(queries);
53+
}
54+
} catch (error) {
55+
// TODO: smarter handling
56+
console.error(error);
57+
}
58+
59+
return {
60+
props: {
61+
dehydratedState: dehydrate(queryClient),
62+
},
63+
};
64+
};
2165

2266
/**
2367
* Page allowing the user to run jobs and applications

pages/project.tsx

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,67 @@
1-
import { useGetProjects } from '@squonk/data-manager-client/project';
1+
import { QueryClient } from 'react-query';
2+
import { dehydrate } from 'react-query/hydration';
23

3-
import { withPageAuthRequired } from '@auth0/nextjs-auth0';
4+
import { getFiles, getGetFilesQueryKey } from '@squonk/data-manager-client/file';
5+
import { getGetProjectsQueryKey, getProjects } from '@squonk/data-manager-client/project';
6+
7+
import { getAccessToken, withPageAuthRequired } from '@auth0/nextjs-auth0';
48
import { css } from '@emotion/react';
59
import { Box, Container, Grid, Typography } from '@material-ui/core';
6-
import dynamic from 'next/dynamic';
10+
import type { GetServerSideProps } from 'next';
711
import Head from 'next/head';
812
import Image from 'next/image';
913

10-
import { CenterLoader } from '../components/CenterLoader';
1114
import Layout from '../components/Layout';
12-
import type { ProjectTableProps } from '../components/ProjectTable';
13-
import type { ProjectFileUploadProps } from '../components/ProjectTable/ProjectFileUpload';
14-
import type { ProjectAutocompleteProps } from '../components/userContext/ProjectAutocomplete';
15+
import { ProjectSelection } from '../components/ProjectSelection';
16+
import { ProjectTable } from '../components/ProjectTable';
17+
import { ProjectFileUpload } from '../components/ProjectTable/ProjectFileUpload';
18+
import { ProjectAutocomplete } from '../components/userContext/ProjectAutocomplete';
1519
import { useCurrentProject } from '../hooks/projectHooks';
1620
import { RoleRequired } from '../utils/RoleRequired';
21+
import { options } from '../utils/ssrQueryOptions';
22+
23+
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
24+
const queryClient = new QueryClient();
25+
26+
try {
27+
const { accessToken } = await getAccessToken(req, res);
1728

18-
const ProjectSelection = dynamic<unknown>(
19-
() => import('../components/ProjectSelection').then((mod) => mod.ProjectSelection),
20-
{ loading: () => <CenterLoader /> },
21-
);
29+
const projectId = query.project as string | undefined;
30+
const path = query.path as string[] | undefined;
2231

23-
const ProjectTable = dynamic<ProjectTableProps>(
24-
() => import('../components/ProjectTable').then((mod) => mod.ProjectTable),
25-
{ loading: () => <CenterLoader /> },
26-
);
32+
if (projectId && accessToken) {
33+
const filesParam = { project_id: projectId, path: '/' + (path?.join('/') ?? '') };
2734

28-
const ProjectFileUpload = dynamic<ProjectFileUploadProps>(
29-
() => import('../components/ProjectTable/ProjectFileUpload').then((mod) => mod.ProjectFileUpload),
30-
{ loading: () => <CenterLoader /> },
31-
);
35+
// Prefetch some data
36+
const queries = [
37+
queryClient.prefetchQuery(getGetProjectsQueryKey(), () =>
38+
getProjects(options(accessToken)),
39+
),
40+
queryClient.prefetchQuery(getGetFilesQueryKey(filesParam), () =>
41+
getFiles(filesParam, options(accessToken)),
42+
),
43+
];
3244

33-
const ProjectAutocomplete = dynamic<ProjectAutocompleteProps>(
34-
() =>
35-
import('../components/userContext/ProjectAutocomplete').then((mod) => mod.ProjectAutocomplete),
36-
{ loading: () => <CenterLoader /> },
37-
);
45+
// Make the queries in parallel
46+
await Promise.allSettled(queries);
47+
}
48+
} catch (error) {
49+
// TODO: smarter handling
50+
console.error(error);
51+
}
52+
53+
return {
54+
props: {
55+
dehydratedState: dehydrate(queryClient),
56+
},
57+
};
58+
};
3859

3960
/**
4061
* The project page display and allows the user to manage files inside a project.
4162
*/
4263
const Project = () => {
4364
const currentProject = useCurrentProject();
44-
const { isLoading } = useGetProjects();
4565

4666
return (
4767
<>
@@ -51,9 +71,7 @@ const Project = () => {
5171
<RoleRequired roles={process.env.NEXT_PUBLIC_KEYCLOAK_DM_USER_ROLE?.split(' ')}>
5272
<Layout>
5373
<Container>
54-
{isLoading ? (
55-
<CenterLoader />
56-
) : currentProject ? (
74+
{currentProject ? (
5775
<>
5876
<Grid
5977
container

pages/results.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ResultCards } from '../components/results/ResultCards';
2222
import { ResultsToolbar } from '../components/results/ResultToolbar';
2323
import { useCurrentProjectId } from '../hooks/projectHooks';
2424
import { RoleRequired } from '../utils/RoleRequired';
25+
import { options } from '../utils/ssrQueryOptions';
2526

2627
// This was a SSR test/example. Not sure if we want to do SSR everywhere but probably should.
2728
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
@@ -32,34 +33,19 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query }
3233

3334
const projectId = query.project as string | undefined;
3435

35-
if (projectId) {
36+
if (projectId && accessToken) {
3637
// Prefetch some data
3738
const queries = [
3839
queryClient.prefetchQuery(getGetProjectsQueryKey(), () =>
39-
getProjects({
40-
baseURL: process.env.DATA_MANAGER_API_SERVER,
41-
headers: { Authorization: `Bearer ${accessToken}` },
42-
}),
40+
getProjects(options(accessToken)),
4341
),
4442

4543
queryClient.prefetchQuery(getGetInstancesQueryKey({ project_id: projectId }), async () =>
46-
getInstances(
47-
{ project_id: projectId },
48-
{
49-
baseURL: process.env.DATA_MANAGER_API_SERVER,
50-
headers: { Authorization: `Bearer ${accessToken}` },
51-
},
52-
),
44+
getInstances({ project_id: projectId }, options(accessToken)),
5345
),
5446

5547
queryClient.prefetchQuery(getGetTasksQueryKey({ project_id: projectId }), () =>
56-
getTasks(
57-
{ project_id: projectId },
58-
{
59-
baseURL: process.env.DATA_MANAGER_API_SERVER,
60-
headers: { Authorization: `Bearer ${accessToken}` },
61-
},
62-
),
48+
getTasks({ project_id: projectId }, options(accessToken)),
6349
),
6450
];
6551

utils/ssrQueryOptions.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { AxiosRequestConfig } from 'axios';
2+
3+
export const options = (accessToken: string): AxiosRequestConfig => ({
4+
baseURL: process.env.DATA_MANAGER_API_SERVER,
5+
headers: { Authorization: `Bearer ${accessToken}` },
6+
});

0 commit comments

Comments
 (0)