Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Common Performance Optimization Instruction #293

Closed
SaltyAom opened this issue Feb 21, 2020 · 0 comments
Closed

Common Performance Optimization Instruction #293

SaltyAom opened this issue Feb 21, 2020 · 0 comments

Comments

@SaltyAom
Copy link

SaltyAom commented Feb 21, 2020

Common Performance Optimization

Hello, it's me again. I'm here to help giving some Common Performance Optimization Instruction. Some of you might recognize me, I think "You again? Why don't you just create a PR yourself??".
Rozaliya

Well... I'm a bit too lazy...

to handle Enterprise-scale project, yet... beside I'm learning some Node.js backend stack which took a long way off. So, I didn't have enough time to learn the whole project and create a PR. So I'll be giving an instruction instead so you could implement it for future use.

First of all, you did a great job optimize common thing. Beside Next.js usually optimize most thing out of the box which you don't have to handle it yourself.

Migrate useCallback, useMemo.

Even those who experience with React missed this step, so I'll start with it.
useCallback and useMemo is introduced to optimized React Function Component (a function which used hooks). Which it's a bit confuse to read, so I'll leave a quick guide here.

React Function Component will re-create itself when a state is updated.

(For more information and reason behind this search about "How React Hooks work").

The thing is, nothing is persisted... which means when you declare a function, it'll just re-create itself each time the state is updated which wasted the computation time and resources.

(I believe developer which has a lot of experience with Data-Structure and Algorithms knows why and might scream it happen).

Good thing, React team introduced useCallback which will help persist these function after state update. Under the hood, React kinda 'cache' your function. So when it re-render it just put from cache prevent a function re-creation.

const Component = () => {
  let [counter, updateCounter] = useState(0)

  // This function will recreate itself everytime when state is updated
  let updateAndDispatchCounter = () => {
    dispatchCounter(counter + 1)
    updateCounter(counter + 1) // Do not mutate state.
  }

  return <button onClick={() => updateCounter()}>{counter}</button>
}

That's waste a lot if there're a lot of state change happening in there, beside in React, we usually use a lot of component.
I have recommended to do this.

const Component = () => {
  let [counter, updateCounter] = useState(0)

  // This function will recreate itself when counter is updated.
  let updateAndDispatchCounter = useCallback(() => {
    dispatchCounter(counter + 1)
    updateCounter(counter + 1) // Do not mutate state.
  }, [counter])

  return <button onClick={() => updateCounter()}>{counter}</button>
}

Just like useEffect, the internal function will update when [dependencies] is changed.
It kinda useless for a single state to have a useCallback but it just an example, I'd recommended using this in multiple state changing happening in there.
Also for a function which doesn't depend on state, I'd recommended adding a blank dependencies, so React can just 'remember' it.

const Component = () => {
  let [counter, updateCounter] = useState(0)

  // This function will be created only on initialization.
  let doSomething = useCallback(() => {
    // Do something that doesn't depend on state.
  }, [])

  return <button onClick={() => doSomething()}>Click me</button>
}

useMemo is recommended for 'remembering' heavy computation but usually used with return.
It kinda implement usage from useCallback plus the syntax itself is just like useCallback.
In your case, you could use useMemo with component which is responsible for displaying user information which retrieve from Firebase. Since most of that usually don't update much.

Migrate memo and PureComponent

I don't know if you knew about memo. It's a PureComponent version of Function Component.
Basically it implement a shouldComponentUpdate which determined if the component should be updated based on props. It compare current props with new props. If it's the same, it'll just skip other life-cycle and return the component's view.
It's not a silver-bullet since if you implement it in component which isn't often follow these rule would just add a O(shallowCompare(props.length)) to the component.
Also, it's not recommended to use this memo and PureComponent in component which is not pure (has state).
You can read more about PureComponent here

Use Dynamic Import

I recognize that you use Next.js to build this project. I'd recommended considering about putting a Component which isn't require in First Contentful Paint to next/dynamic
By doing this, you would get a lot of benefit including:

  • Decreasing total file-size for first pain.
  • Better download time.
  • Well, lazyload (If you know what I'm talking about)
  • Code splitting (I'll explained this on next topic)
  • Better SEO (based on Google's Pagespeed Insight)
It also support SSR out of the box.

image

Dynamic import is good.

You can also use useLazy and Suspense for React Component on client-side which require an interaction eg: Usage with IntersectionObserver which basically, a lazy load component.

Chunk Splitting

I recognize that you're using Zeit Now as a server. By default, Now's server use HTTP/2 Server Push which handle a better job for caching, by overall splitting code into a smaller chunk would performance a better when using with HTTP/2 Server Push and Cache.
I'd recommended that you should splitting a JavaScript and CSS chunk into a smaller piece. Beside, a CSS which generated from StyledComponent with multiple-page app by default doesn't perform tree-shaking as good as it should. I'd recommended checking about that too, otherwise there would be some CSS which doesn't require when using with some pages.

Font display=swap and Google Fonts CDN.

Google Fonts is magic. Adding that ?display=swap property and Google Fonts CDN will send only letter which you used in the page. I don't know what witchery is this and how does Google does it, but it really just working, eg:

http://fonts.googleapis.com/css?family=Roboto&font-display=swap

Pre-render

I don't know if one of you have ever inspect the production code but, it doesn't pre-render.
image

This is critical

I believe Mark already how to fix it. In Next 9.1.7, getStaticProps and getServerProps is introduced for generating a getInitialProps in different usage.
In your src/pages/_document.tsx, you're using getInitialProps for injecting a CSS which generated by StyledComponent into global view which is good.
But... it break pre-rendering which is really bad since React will just created everything on client which require a lot of computation time and memory usage. (If you put an animation on first page it wouldn't even get 60fps) plus it put a great amount of time to render all of it.
I'm not quite sure if it will work but, I'd recommended switching from getInitialProps into getStaticProps so it could just pre-render the page. Even if it getStaticProps doesn't working, I'd recommended to put your best effort to pre-render the page. You could just use React Snap in case getStaticProps doesn't working or just use prerender.io

In case you're wondering why, you might want to read how React construct its component and perform a CPU throttle testing.

Cache Header

Since you're using a Zeit Now server (in fact, any other server should applied this too). You should just add Cache-Control header for any static content which is public and static.

* Files header; Cache-Control: public, max-age=31536000

Use WebP instead of JPG and PNG.

I guess you know why but I'll just explain it again, WebP is a new format for image which is exclusively built for web.

Not THAT much exclusive but still

It reduced a lot of spaces like how gZip helps reduced resources' size.

Enterprise-scale Optimization

An above topic is basically a SHOULD do form most React app. Below this is optional but since you're building an Enterprise-scale app, you should consider applied these technique. (PS. it might be hard tho)
image

Error Boundary.

I don't know how or if you even trace error in your application but for best-practice you should.
Using React's Error Boundary would help you trace a lot of error which you could fixed it later. You could also use (Sentry)[http://sentry.io/] to help tracing errors which occurs.

Compile to static where possible.

I noticed that you rarely use getInitialProps which is only available in SSR. In-case you didn't know, Static Content perform better than SSR does in most way including performance. So, if most of your site are not using a benefit of SSR, I'd strongly recommended you to switch to CSR and use Progressive Hydration where you necessary.
Plus by default Zeit's Now server doesn't support running Nextjs with custom server which you would lose a benefit of Message Queue and custom cache which is REALLY important for Enterprise-scale app.
In case you're wondering why I've never compiled my app to static, I does but never put it on Git. Since I've never deployed (but I practiced it) any Enterprise-scale app publicly, that's why all of my project never compile to static since I'm using Zeit Now, a serverless which doesn't allowed Next.js to run with custom server plus I'm too lazy to do it since I build side-project focusing on practicing Frontend Development.

Plus, I'm too lazy to do it.

Message Queue

I'm too lazy to explain this one, please Google it yourself. It's a topic you should consider.
Jessica almost cry

Cloudflare worker.

I bet you knew Cloudflare so I wouldn't explained much. I'd recommended caching your site with Cloudflare worker if possible, that's all.

Add test.

Too lazy, but you could Google it. I'll leave some important topic.

  • Unit Test
  • Integration Test.
  • Code-coverage.
  • Stress Test.
You might somehow want to use for visual test percy.io.

About Zeit Now

It's perfectly fine running your server on Zeit Now for now...
Just an opinion but I think Zeit Now isn't really suitable for an Enterprise-Scale app. (You should asked Mark about it).
I'd recommended adding CI/CD and just use other provided like Google Cloud or deploys.app and implement your own Kubernetes model. (Mark knows Kubernetes asked him for a help)

Don't ask me, I don't know how to setup a Kubernetes Engine.

That's quite it.

That's quite a common thing you should optimize. Yep...
I might forget something which I might open an issues if I somehow remember it.

And believe me, I will.

image
So, I'll go to sleep and being lazy until then.

@iammarkps iammarkps pinned this issue Feb 24, 2020
@pongsaphol pongsaphol unpinned this issue May 21, 2020
@pongsaphol pongsaphol pinned this issue May 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants