Site Architecture
On this screen, I will walk through some of the design and architectural decisions I made while creating this website. I will also give specific examples of my code. My goal was to create an intuitive, responsive experience that reflects best practices in modern web development and has a robust, scalable code base.

Performance Optimization

I wanted to write this website using React and various third party libraries to showcase my proficiency with them. The downside of this approach, as opposed to a simple HTML page styled with CSS, is that React is pure Javascript. By default, Javascript renders on the user's browser, which means the page takes longer to load, especially the first time a user visits. This is a poor experience for any site, but especially for a personal site which should be fast and lightweight. Therefore, I had to optimize.

Server Components / Code Splitting

• One of Next.js greatest strengths - components that are prebuilt on the server

• Prebuilt components are sent as raw HTML, meaning the browser doesn't need to decode Javascript!

• Therefore, I split up my code to make as much of it render on the server as possible:

1//SERVER COMPONENT
2export default async function ProfileCard() {
3  const t = await getTranslations("Global");
4
5  return (
6    <Card className="mt-5 sm:mt-20 rounded-4xl">
7      <CardContent className="justify-items-center border">
8        <div className="rounded-full overflow-hidden h-50 w-50">
9          <Image
10            className="object-cover w-full h-full"
11            src="/images/profile_picture.webp"
12            alt="Profile picture"
13            height={400}
14            width={400}
15            priority
16          />
17        </div>
18        <Typography className="flex pt-5" variant="titleFont">
19          {t("name")}
20        </Typography>
21        <Typography className="flex" variant="titleFont">
22          {process.env.NEXT_PUBLIC_EMAIL}
23        </Typography>
24        <Typography className="flex" variant="titleFont">
25          {process.env.NEXT_PUBLIC_PHONE}
26        </Typography>
27      </CardContent>
28      <CardActions className="border">
29        <div className="flex w-full justify-between">
30          <LinkedInButton />
31          <ResumeWrapper /> //CLIENT COMPONENT
32          <GithubButton />
33        </div>
34      </CardActions>
35    </Card>
36  );
37}

• MUI components can be rendered on the server by using a special provider in the root layout.tsx file.

1
2export default async function RootLayout({
3  children,
4}: Readonly<{
5  children: React.ReactNode;
6}>) {
7  return (
8    <html
9      lang="en"
10      className="h-full w-full antialiased"
11      suppressHydrationWarning
12    >
13      <body className="h-full w-full flex flex-col">
14        <AppRouterCacheProvider> //PROVIDER
15          <main>
16            {children}
17          </main>
18        </AppRouterCacheProvider>
19      </body>
20    </html>
21  );
22}

• next-intl can fetch translations on the server:

1
2export default async function RootLayout({
3  children,
4}: Readonly<{
5  children: React.ReactNode;
6}>) {
7  const locale = await getCookie(LOCALE_COOKIE_NAME, DEFAULT_LOCALE);
8  const messages = await getMessages({ locale });
9  const architectureMessages = pick(messages, ["Architecture"]);
10  //on the client, you would use the useTranslations() hook instead
11
12  return (
13    <NextIntlClientProvider messages={architectureMessages}>
14      <div className="h-full w-full flex flex-col">{children}</div>
15    </NextIntlClientProvider>
16  );
17}
Dynamic Imports

• Load javascript modules and React components only when they're needed

• Reduces first contentful paint time

• Good for content that needs to be conditionally shown - for example the settings drawer which doesn't render until the user opens it.

1const SettingsDrawer = dynamic(() => import("../settings/settingsDrawer"));
Font

• Bundled with static assets, improving privacy and performance

• Preloaded fonts prevent flashes of unstyled text

• No layout shift - fallback font has same font size

1const ptSerif = localFont({ 
2  src: "../../public/fonts/PTSerif.ttf",
3});
Image

• Automatically converts images to optimal file type - from PNG to WebP for example

• Next.js can either lazy load the images when they're needed or prefetch them

• Prevents layout shift while images are loading

1<div className="rounded-full overflow-hidden h-50 w-50">
2  <Image
3    className="object-cover w-full h-full"
4    src="/images/profile_picture.webp"
5    alt="Profile picture"
6    height={400}
7    width={400}
8    priority
9  />
10</div>
Vercel Speed Insights

• Provided by Vercel - the creators of Next.js and the hosting provider I used to deploy my website

• Injected in the root layout.tsx file, tracks anonymized speed statistics of real users

• Good to see how the website is actually performing in production

Vercel Speed Insights
Lighthouse

• Google Chrome has a built-in performance test.

• Tested on both desktop and mobile devices.

• Mobile tests will throttle your network to simulate a real user on their phone.

Lighthouse Testing

Project Structure

My codebase needed to be intuitive to navigate so I can easily make updates and modifications. It also needed to be scalable, readable, and follow best practices to demonstrate my competency as a developer.

public

src

package.json

Click a folder to see its children and a short description. Not every directory or file is represented - that felt excessive!


Theming
I wanted an easy and consistent way to configure my website's styling. I also wanted to support a dark mode functionality as a simple form of user interaction. MUI provided me with those features, along with great integration with Next.js and many UI components with handy out of the box functionality.

To configure the theme, all I had to do was create a ThemeProvider and give it a theme object with light and dark palettes. Then I used it in my root layout.tsx file and I was ready to go!

1//Wraps the application code in the root layout.tsx file
2export default function MUIThemeProvider({
3  children,
4}: Readonly<{
5  children: React.ReactNode;
6}>) {
7  const customTheme = createTheme({
8    colorSchemes: {
9    //light mode
10      light: {
11        palette: {
12          primary: {
13            main: "#2f82f7",
14            contrastText: "#EDF2FA",
15          },
16          background: {
17            default: "#e4f2ff",
18            paper: "#DDE8FD",
19          },
20          text: {
21            primary: "#0d121b",
22            secondary: "#383D47",
23          },
24        },
25      },
26      //dark mode
27      dark: {
28        //same palette object as light mode - just with different colors
29      },
30    },
31    cssVariables: {
32    //enables manual dark mode toggling
33      colorSchemeSelector: "class",
34    },
35    //inject my custom font to use in MUI components
36    typography: {
37      bodyFont: {
38        fontFamily: "ibmPlexSans",
39      },
40    },
41  });
42  return (
43    <ThemeProvider theme={customTheme}> 
44      <CssBaseline /> 
45      {children} 
46    </ThemeProvider>
47  );
48}

MUI provides many components with many configuration options. The components use my MUI theme and greatly reduce the amount of boilerplate code I have to write. Best of all, these components automatically use the colors I configured in my ThemeProvider.

1//MUI Drawer component
2import Drawer from "@mui/material/Drawer";
3
4export default function SettingsDrawer(props: {
5  settingsDrawerOpen: boolean;
6  setSettingsDrawerOpen: React.Dispatch<React.SetStateAction<boolean>>;
7}) {
8  const { settingsDrawerOpen, setSettingsDrawerOpen } = props;
9
10  //a settings drawer that can be toggled open and closed
11  return (
12    <Drawer
13      open={settingsDrawerOpen}
14      onClose={() => setSettingsDrawerOpen(false)}
15      anchor="right"
16    >
17      //render any React content inside the drawer
18    </Drawer>
19}
1"use client";
2//MUI imports
3import Tab from "@mui/material/Tab";
4import Tabs from "@mui/material/Tabs";
5
6import { disclosureTabList } from "@/src/constants/disclosuresConstants";
7import { useTranslations } from "next-intl";
8import { useState } from "react";
9import PrivacyContent from "./privacyContent";
10import dynamic from "next/dynamic";
11const AttributionContent = dynamic(() => import("./attributionContent"));
12
13export default function DisclosuresContent() {
14  const t = useTranslations("Disclosures");
15  const [disclosureTabValue, setDisclosureTabValue] = useState(0);
16  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
17    setDisclosureTabValue(newValue);
18  };
19
20  //render a pair of tabs and conditionally show different content based on which tab is selected
21  return (
22    <>
23      <Tabs value={disclosureTabValue} onChange={handleChange}>
24        {disclosureTabList.map((tab) => (
25          <Tab label={t(tab)} key={tab} />
26        ))}
27      </Tabs>
28      {disclosureTabValue == 0 && <PrivacyContent />}
29      {disclosureTabValue == 1 && <AttributionContent />}
30    </>
31  );
32}

These examples merely scratch the surface of what MUI is capable of: it's a powerful, constantly evolving framework used by Costco, Marriott, Medium, and more. It was useful while developing this website and I'm excited to keep learning and using it in the future.


Localization
I used next-intl for localization because it's easy to configure, seamlessly integrates with Next.js client and server components, and allows me to easily add more languages and switch between them.

To store the strings, I create json files for each language. The nested json objects allow a screen to only fetch the strings it needs. For example, this screen is using the 'Architecture' strings.

1{"Global": {
2    "name": "Ilya Belegradek",
3  },
4  "Architecture": {
5    "hello_world": "Hello world!",
6  },

This is my request.ts file that actually configures the strings based on the locale the user selected.

1import { getRequestConfig } from "next-intl/server";
2  import { cookies } from "next/headers";
3  import {
4    DEFAULT_LOCALE,
5    LOCALE_COOKIE_NAME,
6  } from "../constants/localeConstants";
7  
8  export default getRequestConfig(async () => {
9    const store = await cookies();
10    const locale = store.get(LOCALE_COOKIE_NAME)?.value || DEFAULT_LOCALE;
11  
12    return {
13      locale,
14      messages: (await import(`../../public/strings/${locale}.json`)).default,
15    };
16  });

Then I wrap a layout file for a screen in the next-intl provider and I'm ready to go! All I have to do is call useTranslations() - or await getTranslations() in a server component - and I can fetch any string by its key. When the locale switches, it automatically pulls the correct string from the new json.

1export default async function RootLayout({
2    children,
3  }: Readonly<{
4    children: React.ReactNode;
5  }>) {
6    const locale = await getCookie(LOCALE_COOKIE_NAME, DEFAULT_LOCALE);
7    const messages = await getMessages({ locale });
8    const architectureMessages = pick(messages, ["Architecture"]);
9  
10    //pass only the necessary messages to the provider
11    return (
12      <NextIntlClientProvider messages={architectureMessages}>
13        <div className="h-full w-full flex flex-col">{children}</div>
14      </NextIntlClientProvider>
15    );
16  }

PDF Downloading
I wanted my website to allow users to download a PDF of my resume to hopefully aid in my job search. Also, I didn't enjoy creating a resume using a word processor, so I wanted to program a resume instead. The @react/pdf-renderer library fit my purposes perfectly.

• The library uses a React Native like syntax that's easy to pick up for any React developer.

• Custom components - Text, View, etc. - are used to organize and render content on the PDF

• StyleSheets are used to apply CSS like styling to those components

• Page will output to a standard single page in the PDF - overflowing onto the next one if I run out of room.

1import { Document, Page, Text, View } from "@react-pdf/renderer";
2
3export default function ResumePDF(props: {
4  t: ReturnType<typeof useTranslations>;
5}) {
6  const { t } = props;
7
8  return (
9    <Document>
10      <Page size="A4" style={resumeStyles.page}>
11        <View style={resumeStyles.header}>
12          <Text style={resumeStyles.name}>{t("name")}</Text>
13        </View>
14      </Page>
15    </Document>
16  )
17}
1import { StyleSheet, Font } from "@react-pdf/renderer";
2
3//custom font support
4Font.register({
5  family: "titleFont",
6  src: "/fonts/PTSerif.ttf",
7});
8
9export const resumeStyles = StyleSheet.create({
10  page: {
11    flexDirection: "column",
12    padding: 8,
13    fontFamily: "titleFont",
14    fontSize: 11,
15  },
16  header: {
17    flexDirection: "row",
18    justifyContent: "space-between",
19    borderBottom: 2,
20    borderBottomColor: "black",
21    borderBottomWidth: 1,
22    marginBottom: 4,
23    paddingBottom: 2,
24  },
25  name: {
26    fontSize: 32,
27    fontWeight: 700,
28    textAlign: "center",
29    flex: 1,
30    paddingTop: 8,
31    paddingLeft: 20,
32  },
33

Code Blocks
I wanted to display code snippets on this screen to demonstrate the exact functionalities I was describing. At first, I tried screenshots, but they were blurry and low resolution. Then I found the React Syntax Highlighter library, which allows me to easily render and style code.

All I had to do was install the npm library, make this reusable React component, and I was ready to go. Every chunk of code on this screen was displayed with this component.

1import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
2import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
3
4export default function CustomCodeBlock(props: { code: string }) {
5  const { code } = props;
6
7  return (
8    <SyntaxHighlighter
9      language="typescript"
10      style={vscDarkPlus}
11      showLineNumbers={true}
12      customStyle={{
13        borderRadius: "8px",
14        padding: "20px",
15        maxWidth: "fit-content",
16      }}
17      wrapLongLines={true}
18    >
19      {code}
20    </SyntaxHighlighter>
21  );
22}