Frontend Development
Understand and document the important concepts in front end development
- Client side vs Server side rendering.
- Understand metrics that matter.
- What is good performance.
- MPA(multi-page applications) vs SPA(single page environments).
- Server environments and runtimes(js)
- State Management.
Frontend Frameworks
what problem do they solve, alternatives, choosing
- Change in application state requires a update rerender of the User Interface.
- Reduce complexity involved in working with the DOM. / make elements, change properties, put el in other els, get them on page, handle user interactions,.
- Introduce a declarative approach to building UIs.
- Frameworks come with tooling that helps with testing, linting among other functions.
- Compartmentalization.
- Routing. (server-side routing vs client-side routing)
Rendering Strategies.
-
Where and How should it be rendered?? Client or Server, Partially or Progressively
-
Choosing the correct pattern could lead to faster builds and excellent loading performance at low processing costs.
-
To create great UX, usually try to optimize for user-centric metrics such as Core Web Vitals(CWV).
-
CWV metrics measure parameters most relevant to user experience and optimizing it can help ensure a great user experience and optimal SEO for our apps.
-
For great DX, we have to optimize our development environments by ensuring faster build times, easy rollbacks, scalable infrastructure, low server costs, dynamic content ad reliable uptime
-
Important to remember that every pattern was designed to address specific use cases, can be likely that different rendering patterns are required for pages on the same site.
-
Client-Side Rendering.
- render individual pages all in the client's browser.
- use web apis provided by modern browsers.
- poor seo as se don't execute js.
- CSR resulted in large Js bundles which increased the FCP and TTI of the page.
- load can get huge as js is being executed hence splash screens.
- entire web application is loaded on first request, no new request for rendering the pages.
- Hence allows us to have SPA that support navigation without page refresh and provides a great user experience.
- Allows for clear separation of client side and server side code.
- Pitfalls
- SEO considerations
- Performance - initial load times affected.
- Code maintainability
- Data Fetching - increase load/interaction time of the application.
- Optimizations
- Budgeting Javascript - tight js budget for your initial page loads, 100kb-170kb.
- Preloading - preload critical resources earlier in page lifecycle.
- Lazy loading - non-critical resources loaded when needed.
- Code splitting - lazy-load js resources
- Application shell caching with service workers - mostly offline.
-
Server-Side Rendering.
- computes all html before responding and delivering a full page.
- connect and fetch operations are handled on the server, HTML required to format the content is also generated on the server.
- avoid making roundtrips for data fetching and templating, thus rendering code is not required on the client and Js need not be sent to the client.
- every request is treated and processed differently.
- provided additional budget for client-side Javascript
- full page reloads required for some interactions.
- React for the server
- ReactDOMServer.renderToString(element)
- used with ReactDOM.hydrate() - rendered html is preserved as is on the client and only event handlers attached after load.
- implemented with a .js file both on client and server, with server rendering it and client hydrating it.
- seo friendly.
- ideal for highly dynamic and personalized pages that are different for every user.
- content is unique and should not be cached by the CDN.
- most metrics are fast.
- ttb slower as all computations are done.
- Optimizations
- execution time of getServersideprops
- deploy database in same region as functions.
- add cache-control headers
- upgrade server hardware.
- Edge SSR + HTTP streaming.
- Streaming also enables React Server Components, thus allowing for a good hybrid between static and server rendering.
- rendertoNodeStream() -allows us to send our application in smaller chunks.
- Performance improvements - TTFB
- Handles backpressure
- Support SEO.
-
Static Site Generation.
- Precomputes dynamic pages up front.
- all metrics served well too.
- lower costs too and excellent scalability.
- changes require rebuilding of the site.
- Variations
- Basic/Plain Static Rendering.
- little to no dynamic content.
- no layout shift and fast LCP and FCP.
- goes extremely wel with CDN for caching.
- Static Rendering with Client-Side fetch
- best for when you want to update some data on every request.
- good TTFB and FCP but LCP is sub-optimal.
- possibility of layout shifts
- High server costs
- Static with getStaticProps
- allow us to generate HTML with data on the server
- Incremental Static Regeneration
- allows us to pre-render only certain static pages and render the dynamic pages on-demand when user requests them.
- results in shorter build times and allows automatic invalidation of the cache and regeneration of the page after a specific interval.
- can use a revalidate field to control how the landing page gets refreshed.
- stale-while-revalidate strategy.
- On-demand Incremental Static Regeneration
- solves the ISR of that not every page needs to be regenerated and its cache invalidated.
- this occurs on certain events rather than on fixed intervals, i.e on new data availability.
- Basic/Plain Static Rendering.
-
Progressive Hydration
- Hydration - attaching of listeners and handlers
- Individually hydrate the nodes over time, making it possible to request only minimum js.
- Also allow for prioritization of nodes.
- Provide great performance by activating your app in chunks.
- Reduce bundle size
- Requirements for hollistic progressive hydration
- allows usage of SSR for all components.
- supports splitting of code into individual components or chunks.
- supports client-side hydration of these chunks in a developer defined sequence.
- does not block user input on chunks that are already hydrated.
- allows usage of some sort of a loading indicator for chunks with deferred hydration.
- Reacr concurrent mode - allow React to work on different tasks and switch between them based on a given priority.
-
Hybrid rendering strategies.
- universal/isomorphic apps, hydration and client-side fetching.
-
React Server Components
- aim is to enable modern UX with a server-driven mental model.
-
-
Selective Hydration
- pipeToNodeStream()
-
Islands Architecture
- encourages small, focused chunks of interactivity within server-rendered web pages.
- aims to reduce the volume of Js shipped via islands of interactivity that can be independently delivered on top of otherwise static HTML.
-
View Transitions
- chrome
React
- Helps build complex and tricky interfaces by organizing your interface into three key concepts; -
- Components
- js functions that accept input(props) and return react elements describing what should be displayed on the screen.
- Props
- refer to the internal data of a component in React.
- determine value of prop before component building
- props are read-only
- State.
- object that holds some information that may change over the lifetime of the component.
- current snapshot of data stored in a component's props.
- data may change over time so techniques to manage the way data changes become necessary hence state management.
- variable initialized and managed by the component.
- Lifecycle
- every react component goes through three stages; mounting, rendering and dismounting.
- series of events that occur during these three stages can be referred to as component lifecyle.
- render(), componentDidMount(), componentWillMount(), shouldComponentUpdate(), componentDidUpdate()
- Higher Order Components
- takes in a component and returns a component
- Context
- share data between components without explicitly passing down props through every level of hierarchy.
- React Hooks
- functions that let you hook into React state and lifecycle features from functional components.
- let you use state and other React features without writing a class.
- Components
- Composition-based hence perfectly map to your design system.
Metrics
- Time to First Byte (TTFB)
- Used to determine the load on the server and the network between client and server.
- time it takes a client to receive the first byte of page content.
- First Paint.
- Included TTFB and the computation client has to do before rendering job.
- First Contentful Paint
- Marks time browser takes to render the first text or image after navigation.
- Largest contentful Paint
- Time until user sees expected results/main page content.
- Time to Interactive
- Time taken for page to go from loading to fully interactive.
- Cumulative Layout Shift
- measures visual stability to avoid unexpected layout shift
- First Input Delay
- Time from when user interacts with the page to the time when the event handlers are able to run.
State Management
- Scalars(booleans,numbers,strings) vs References(objects, arrays) handled differently.
- useState, useReducer, useMemo, useCallback, useEffect, useRef
- Context and Custom hooks.
- Libraries: Zustand, Valtio, Jotai
Source Maps
- Provides a way of mapping code within a compressed file back to its original position in a source file.
- Undos the from Module -> Compiler -> Assets process.
- Browsers parse it to give back an almost accurate representation of the code, i.e debugging, stack traces.
- Not automatically donwloaded or included with web pages.
- Transpilation too.
- Base64 VLQ
- Optimized to make it easier to have mapping between big numbers and corresponding information in source maps.
- A line of code is represented in a series of segments
Patterns for Web Apps
- Routing
- Server
- Client
- Hybrid
- Rendering
- Client-side
- SSR(streaming, async, shell)
- Static
- Hydration
- Eager
- Full
- Replayable
- Progressive
- Load, Bundle/Serialize, Execute on load
Computational Caching
- Pure Functions
- for a given set of parameters they always return the same value
- do not trigger side effects
- Memoization
- result caching in process
- Serialization and Hashing
- serialization: turning data into sth that can be stored.
- hashing: taking data and scrambling it.
- Persistence
- saving to disk.
Performance Patterns
- critical gap between developer expectations and how the browser prioritizes resources on the page.
- Causes of gap
- Sub-optimal sequencing.
- understand metrcis well and what they stand for and the order of occurence.
- FCP before LCP before FID, hence resources should be allocated in that order too.
- Network/CPU utilization
- Third-party products.
- Platform quirks
- different browser prioritization.
- HTTP2 prioritization
- Resource level optimization
- Sub-optimal sequencing.
- Resource-wise recommendations
- Critical CSS.
- Fonts
- Above the Fold images
- Below the Fold images
- 3P Js
- Static Import
- import code exported by another module via import keyword.
- Dynamic Import
- dynamically import components using React Suspense.
- lazy() keyword.
- loadable components
- Import on visibility
- intersectionObserver API
- react-loadable-visibility
- Import on interaction
- load non-critical resources when user interacts with UI requiring it.
- ways to load different resources
- eager - load right away.
- lazy(route-based)
- lazy(on interaction)
- lazy(in viewport)
- prefetch - load prior to needed, but after critical resources are loaded.
- preload - eagerly with greater level of urgency.
- fake loading with a UI facade
- video embeds
- authentication
- chat widgets
- implemented using the dynamic import(), which enables lazy loading of modules and returns a promise.
- can combine it with the suspense component.
- uncanny valley- page looks ready but user unable to tap anything.
How does one get import-on-interaction right??
- Understand trade-offs
- Replacing interactive embeds with a static variant.
- Route-based splitting
- dynamically load components based on the current route.
- Bundle splitting
- split code into small reusable pieces.
how to efficiently do this??
- PRPL pattern
- Optimise initial load through precaching, lazy loading and minimizing roundtrips.
- main considerations
- pushing critical resources efficiently, which minimizes the amount of roundtrips to the server and reducing the loading time.
- rendering the initial route soon as possible to improve user experience.
- pre-caching assets in the background for frequently visited routes to minimize the amount of requests to the server and enable a better offline experience.
- lazily loading assets and routes that aren't frequently requested.
- uses service workers to cache resources.
- Tree Shaking
- reduce bundle size by eliminating dead code.
- web bundlers job.
- Preload
- inform the browser of critical resources before they are discovered.
- preload + async
- Prefetch
- fetch and cache resources that may be requested some time soon.
- only do this for necessary resources.
- Optimize loading third party
- reduce performance impact third-party scripts have on your site.
- List virtualization
- optimise list performance with list virtualization
- also known windowing i.e using react-virtualized or react-window.
look into the libraries above...very interesting concept
- CSS-content visibility works along the same lines.
- Compressing Javascript
- reduce time needed to transfer scripts over the network.
- Js is second biggest contributor to page size and the second most requested web resource after images.
- We use patterns that reduce transfer, load and execution time for Js to improve website performance.
- Can combine compression with other techniques such as minification, code-splitting, bundling, caching and lazy-loading though these techniques may be at odds sometime.
- Gzip and Brotli most common.
- Brotli offers better compression ratio.
- Next.js provides Gzip compression by default but recommends enabling it on proxy.
- Single large bundle gives better compression than multiple smaller ones.
- HTTP compression(Accept-Encoding Header -> Content Encoding)
- Minification - remove whitespace and unnecessary code.(Terser, Webpack)
- Static(ahead-of-time) vs Dynamic(on-the-fly) compression.
- Granularity trade-off
- Improve donwload speed
- Improve cache hits and caching efficiency
- Execute Fast
Granular chunking
Design Patterns
- Singleton Pattern
- share a single global instance throughout our application.
- use variable equal to a reference to instance when a new one is created.
- Object.freeze makes sure consuming code cannot modify singleton.
- Tradeoffs
- save on memory, as only one needed.
- singleton considered anti-pattern.
- testing is hard
- dependency hiding
- global behaviour
- State management in React, global state over singletons, read-only as opposed to mutable state of singleton.
- Proxy Pattern
- intercept and control interactions to target objects.
- new Proxy(obj, {get, set}), {} reps handler.
- useful to add validation.
- reflect built-in object to work with target object.
- formatting, notifications or debugging.
- not recommended for performance code.
- Provider Pattern
- make data available to multiple child components.
- createContext()
- value prop that can carry data to all components wrapped within this provider.
- useContext hook
- useful for sharing global data, i.e UI theme data.
- Major con is components have to consume the context to re-render on each state change
- Prototype Pattern
- share properties among many objects of the same type.
- easily let objects access and inherit properties from other objects.
- Container/Presentation Pattern
- enforce separation of concerns by separating view from the application logic.
- presentation - how vs container - what
- container elements pass data to presentational elements.
- Hooks though made this easier as they act as container elements themselves.
- Observer Pattern
- use observable to notify subscribers when an event occurs.
- subscribe certain objects, observers, to another object, observable.
- observable object contains 3 important parts
- observers - arrays of objects to be notified.
- subscribe()
- unsubscribe() - remove observers
- notify() - fire when event happens
- useful in asynchronous, event-based data.
- RxJs
- prone to complexity.
- Module Pattern
- split up your code into smaller, reusable pieces.
- named exports then import{} from where/they/are
- for cases of collision, we can use rename using as.
- could also use
default export
, although only one per module. - also import using * for all exports.
- dynamic import using import() which can receive expressions allowing for passing template literals.
- Mixin Pattern
- Add functionality to objects or classes without inheritance
- A mixin is an object we can use in order to add reusable functionality to another object or class without using inheritance.
- Mixins can't be used on their own.
- Object.assign(obj.prototype, mixin)
- Mediator/Middleware Pattern
- use a central mediator object to handle communication between components.
- prevent all objects from talking to every other object, many-to-many relationship
- HOC Pattern
- pass reusable logic down as props to components throughout your application
- logic can be styling components, authorization, adding global state.
- HOC is a component that receives another component, HOC contains logic that we want to apply to passed parameter component, returning component after applying logic.
- Render Props Pattern
- pass jsx elements to components through props.
- a render prop is a prop on a component, which value is a function that returns a JSX element.
- props.render()
- render props over lifting state
- Hooks Pattern
- use functions toreuse stateful logic among multiple components throughout the app.
- manage a component state and lifecycle methods
- make it possible to
- add state to functional component
- manage a component lifecyle without having to use lifecycle methods
- reuse the same stateful logic among multiple components throughout the app.
- add state using useState()
- [value, setValue] = useState(initValue);
- useEffect combines DidMount,DidUpdate,WillUnmount
- uses a dependancy array.
- Flyweight Pattern
- reuse existing isntances when working with identical objects.
- Factory Pattern
- use a factory function in order to create objects
- Compound Pattern
- create multiple components that work together to perform a single task.
- make one component a property of another component.
- Command Pattern
- decouple methods that execute tasks by sending commands to a commander.
- replace class methods with one that executes any command passed to it.