Upgrade to Pro — share decks privately, control downloads, hide ads and more …

๐Ÿ‡ฉ๐Ÿ‡ช iJS Munich 2023 - Speed at Scale: Optimizing...

๐Ÿ‡ฉ๐Ÿ‡ช iJS Munich 2023 - Speed at Scale: Optimizing The Largest CX Platform Outย There

In a world with a wide variety of network connections, any user can have a slow experience, and apps that delight users on fast connections can be barely usable on slow ones.

In this session, I'll share my journey of gradually adapting how we deliver pages to better cater to our user's constraints on a platform with 15mi+ sessions per monthโ€”where simply rewriting everything from scratch is not an option.

With an eye on maintenance and scalability, we'll discuss legacy codebases, automation/code generation, polyfilling strategies, main thread offloading, and many other tips and tricks for large platforms targeting emerging markets.

Matheus Albuquerque

October 25, 2023
Tweet

More Decks by Matheus Albuquerque

Other Decks in Programming

Transcript

  1. Hello, Munich! ๐Ÿ‘‹ ๐Ÿ‡ฉ๐Ÿ‡ช SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE โ€ข THE 25TH OF OCTOBER, 2023.
  2. โ†‘ ALL THE LINKS! ๐Ÿง‘๐Ÿซ TECHLABS ๐Ÿฆ @ythecombinator ๐Ÿ‘จ๐Ÿ’ป MEDALLIA

    โšก PERF GDE SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE /
  3. [โ€ฆ] โ€œWhile browsing HackerNews, I sometimes get the feeling that

    every developer out there is working for FAANG, as there are always posts from those people doing some hyped stuff. Or you might think that PHP is never used nowadays because whenever itโ€™s mentioned, everyone is hating on it in the comments.โ€ [โ€ฆ] โ€”โ€‰The silent majority, by Vadim Kravcenko
  4. [โ€ฆ] โ€œBut letโ€™s be straight, thatโ€™s like 1% of all

    of the developers out there โ€” the rest of them are just lurking and coding with their language of choice and being content with it. Be it Fortran, COBOL, Perl, or PHP.โ€ [โ€ฆ] โ€”โ€‰The silent majority, by Vadim Kravcenko
  5. This talk is not aboutโ€ฆ โ† React 18, Svelte 5,

    Vue 3โ€ฆ โ† ISG, SSR, Streaming SSRโ€ฆ โ† Progressive/Selective/Partial Hydrationโ€ฆ โ† Islands Architecture, Resumabilityโ€ฆ
  6. YOU ALREADY KNOWโ€ฆ โ† GZIP EVERYTHING โ† MINIFY BOTH CSS

    AND JS โ† CSS GOES ON <HEAD> โ† JS GOES LASTLY ON </BODY> โ† OPTIMIZE ALL THE IMAGES
  7. YOU ALREADY KNOWโ€ฆ โ† HTTP/2 (REQ/RES MULTIPLEXING; EFFICIENT COMPRESSION; REQUEST

    PRIORITIZATION; SERVER PUSH) โ† IMAGE CDNS (40โ€“80% SAVINGS; LESS WORK WITH TRANSFORMATION, OPTIMIZATION, AND DELIVERY) โ† OPTIMIZE THIRD-PARTIES (DEFER/REPLACE/UPDATE)
  8. This talk is aboutโ€ฆ โ† Optimizing JavaScript Delivery โ† Shipping

    Less Code โ† Targeting Different Browsers โ† Ongoing & Future Work โ† Closing Thoughts
  9. This talk is also aboutโ€ฆ โ† Code Generation โ† Polyfills

    โ† Problems โ† Experiences โ† Lessons Learned
  10. OPTIMIZING JAVASCRIPT DELIVERY TRANSFERRING SCRIPTS IN AN EFFICIENT WAY. NETWORK

    UTILIZATION NOT BLOCKING THE MAIN THREAD TOO MUCH. SCRIPT EXECUTION
  11. OPTIMIZING JAVASCRIPT DELIVERY TRANSFERRING SCRIPTS IN AN EFFICIENT WAY. NETWORK

    UTILIZATION NOT BLOCKING THE MAIN THREAD TOO MUCH. SCRIPT EXECUTION
  12. REACT 15 ใฑบ 16 SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  13. REACT 15 ใฑบ 16 โ† react = 5.3 KB (2.2

    KB GZIPPED), DOWN FROM 20.7 KB (6.9 KB GZIPPED) โ† react-dom = 103.7 KB (32.6 KB GZIPPED), DOWN FROM 141 KB (42.9 KB GZIPPED) โ† A COMBINED 32% SIZE DECREASE COMPARED TO THE PREVIOUS VERSION (30% POST-GZIP)
  14. WEBPACK 1 ใฑบ 5 SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  15. WEBPACK 1 ใฑบ 5 โ† IMPROVED BUNDLE SIZES USING BETTER

    TREE SHAKING AND CODE GENERATION โ† IMPROVED PERFORMANCE BY LEVERAGING PERSISTENT CACHING AND LONG-TERM CACHING โ† IMPROVED LONG-TERM CACHING WITH BETTER ALGORITHMS AND DEFAULTS
  16. โ€”โ€‰MIGRATING TO WEBPACK 5 TO IMPROVE BUILD TIME AND REDUCE

    CHUNK SIZES โ€ข MAYANK KHANNA WEBPACK 1 ใฑบ 5
  17. WEBPACK 1 ใฑบ 5 โ† NamedModulesPlugin = > optimization.moduleIds: 'named'

    โ† NamedChunksPlugin = > optimization.chunkIds: 'named' โ† HashedModulesPlugin = > optimization.moduleIds: 'deterministic'
  18. #1 of 3 MIGRATION IS ABOUT HAVING ALL THE PLUGINS

    AND LOADERS ON THEIR LATEST VERSIONS AND ENSURING THAT EVERY LOADER AND PLUGIN IS COMPATIBLE WITH EACH OTHER. #GOTCHA ๐Ÿคท SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / SHIPPING LESS CODE / WEBPACK
  19. #2 of 3 V5 MIGRATION CAN'T BE DONE FROM V2/V3.

    IF YOU'RE ON A VERSION LOWER THAN V4, THATโ€™S AN ENTIRELY DIFFERENT MIGRATION THAT NEEDS TO BE FOLLOWED ONE AFTER THE OTHER SEQUENTIALLY. #GOTCHA ๐Ÿคท SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / SHIPPING LESS CODE / WEBPACK
  20. #3 of 3 NODE 10.13.0 IS THE MINIMUM REQUIREMENT FOR

    V5 BUT THERE ARE SOME PLUGINS AND LOADERS THAT REQUIRE HIGHER NODE VERSIONS โ€” E.G. copy-webpack- plugin REQUIRES NODE โ‰ฅ 12.20.0. #GOTCHA ๐Ÿคท SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / SHIPPING LESS CODE / WEBPACK
  21. NODE 10 ใฑบ 16 SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  22. REACT 15 ใฑบ 16 SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  23. #definition ๐Ÿง Jscodeshift is a tool that runs a transformation

    script over one or more JavaScript or TypeScript files.
  24. #definition ๐Ÿง Jscodeshift reads all the files you provide to

    it at runtime and analyzes and compiles the source into the AST as part of the transformation. It then looks for matches specified in the transformation script, deletes them, or replaces these with the required content, and then regenerates the file from the modified AST.
  25. WE HAD TO SUPPORT, IN PARALLEL: โ† DIFFERENT VERSIONS OF

    DEVELOPER DEPENDENCIES (E.G. node.js, Webpack, Babel, ETC.) โ† DIFFERENT VERSIONS OF APPLICATION DEPENDENCIES (E.G. React ITSELF) โ† DIFFERENT STRATEGIES USED FOR BUNDLING AND SERVING CODE GENERATION: AFTER
  26. CODE GENERATION: AFTER REPO / PACKAGES / (LEGACY) A (LEGACY)

    B NEXT (CONFIG + SCRIPTS)* NEXT (CODE GENERATED) โ€ฆ
  27. CODE GENERATION: AFTER REPO / PACKAGES / (LEGACY) A (LEGACY)

    B NEXT (CONFIG + SCRIPTS)* NEXT (CODE GENERATED) โ€ฆ โ† DEFAULT BUNDLE THATโ€™S SERVED โ† ACTIVE DEVELOPMENT (IE. NEW FEATURES/FIXES) HAPPENS HERE โ† THIS IS THE CODE THATโ€™S PUSHED TO THE REPO โ† LEGACY DEPENDENCIES: REACT 15, WEBPACK 1, NODE 10, ETC.
  28. CODE GENERATION: AFTER REPO / PACKAGES / (LEGACY) A (LEGACY)

    B NEXT (CONFIG + SCRIPTS)* NEXT (CODE GENERATED) โ€ฆ โ† HIDDEN BEHIND A FEATURE FLAG โ† MODERN DEPENDENCIES: REACT 16, WEBPACK 5, NODE 18, ETC. โ† MOSTLY COMPOSED OF CODE TRANSFORMERS, AUTOMATION SCRIPTS, AND WEBPACK/BABEL CONFIG โ† ALSO, PARTS OF THE CODE THAT CAN'T BE CODE GENERATED (IE. UNIT AND FUNCTIONAL TESTS)
  29. CODE GENERATION: AFTER REPO / PACKAGES / (LEGACY) A (LEGACY)

    B NEXT (CONFIG + SCRIPTS)* NEXT (CODE GENERATED) โ€ฆ โ† GENERATED AT BUILD TIME (IE. CI/LOCALLY) โ† REACT COMPONENTS, UTILS, AND OTHER BUSINESS LOGIC โ† THIS CODE IS NEVER PUSHED (1K+ LINES IN GITIGNORE FILE)
  30. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / SHIPPING LESS CODE / CODE GENERATION: TRANSFORMERS
  31. export default function transformer(file: FileInfo, api: API) { const j

    = api.jscodeshift; const root = j(file.source); const variableDeclarators = root.findVariableDeclarators('foo'); variableDeclarators.renameTo('bar'); return root.toSource(); } CODE GENERATION: AST
  32. CODE GENERATION: TRANSFORMERS const transformGroups = [ { label: "PropTypes

    โ†’ prop-types", pattern: "./src/ * /.{js,jsx}", transformerPath: proptypes, }, { label: "legacy-testing/src/testUtils โ†’ ./utils/test", pattern: "./src/ * /.jsx", transformerPath: testUtils, }, { label: " * .scss โ†’ * .global.scss", pattern: "./src/ * /.jsx", transformerPath: cssGlobals, }, ];
  33. const applyTransformer = async ( transformerPath: string, pattern: string |

    string[] ) = > { const paths = await globby(pattern); const options = { silent: true, }; try { await runTransformer(transformerPath, paths, options); } catch (err) { logger.error(err as string); } }; CODE GENERATION: TRANSFORMERS
  34. for (const item of transformGroups) { logger.info(`\n๐Ÿ›  Appying the '${item.label}'

    transform`); await applyTransformer(item.transformerPath, item.pattern); } CODE GENERATION: TRANSFORMERS
  35. const testUtilsAbsolutePath = resolve("src/utils/test"); const testUtilsPathNormalizer = (path: string) =

    > path.substring(path.indexOf("/") + 1); const pathTransformer = (filePath: string) = > (importPath: ASTPath<ImportDeclaration>) = > { const fileAbsolutePath = resolve(filePath); const diff = relative(fileAbsolutePath, testUtilsAbsolutePath); importPath.value.source.value = testUtilsPathNormalizer(diff); }; CODE GENERATION: TRANSFORMERS
  36. export default function transformer(file: FileInfo, api: API) { const j

    = api.jscodeshift; const root = j(file.source); return root .find(j.ImportDeclaration, { source: { value: "legacy-testing/src/testUtils" }, }) .forEach(pathTransformer(file.path)) .toSource(); } CODE GENERATION: TRANSFORMERS
  37. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / SHIPPING LESS CODE / CODE GENERATION: TRANSFORMERS
  38. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / SHIPPING LESS CODE / CODE GENERATION: TRANSFORMERS
  39. CODE GENERATION: FILES SPEED AT SCALE: OPTIMIZING THE LARGEST CX

    PLATFORM OUT THERE / SHIPPING LESS CODE /
  40. CODE GENERATION: FILES const copyGroups = [ { label: "legacy/bin",

    source: " . . / . . /packages/legacy/bin", destination: "./bin", }, { label: "legacy/stub", source: " . . / . . /packages/legacy/stub", destination: "./stub", }, { label: "legacy-testing/src/utils", source: " . . / . . /packages/legacy-testing/src/utils", destination: "./src/utils/test", }, / / . . . ];
  41. const copyGroups = [ / / . . . {

    label: "legacy/src", source: " . . / . . /packages/legacy/src", destination: "./src", pattern: [ " / / .test.jsx", " / / .test.js", " / / .spec.jsx", " / / .spec.js", " / / .snap", ], filter: "!match" as const, } ]; CODE GENERATION: FILES
  42. const copyFiles = async (config: CopyGroup | EnhancedCopyGroup) = >

    { const sourcePath = resolve(config.source); const destinationPath = resolve(config.destination); try { if (isEnhancedCopyGroup(config)) { await copy(sourcePath, destinationPath, { filter: matchFile(config) }); } else { await copy(sourcePath, destinationPath); } } catch (err) { logger.error(err as string); } }; CODE GENERATION: FILES
  43. for (const item of copyGroups) { logger.info(`\n๐Ÿšš Copying the '${item.label}'

    directory`); await copyFiles(item); } CODE GENERATION: FILES
  44. const renameGroups = [ { label: "anchor.scss", source: "/ *

    /anchor.scss", destination: "anchor.global.scss", }, { label: "anchorBar.scss", source: "/ * /anchorBar.scss", destination: "anchor.global.scss", }, ]; CODE GENERATION: RENAMING
  45. const renameFiles = async (pattern: string | string[]) = >

    { const paths = await globby(pattern); for (const path of paths) { const file = getLastPathItem(path); const [fileName, fileExtension] = splitFileName(file); const newPath = path.replace(file, `${fileName}.global.${fileExtension}`); try { await rename(path, newPath); } catch (err) { logger.error(err as string); } } }; CODE GENERATION: RENAMING
  46. for (const item of renameGroups) { logger.info(`\nโœ Renaming the the

    following item: '${item.label}'`); await renameFiles(item.source); } CODE GENERATION: RENAMING
  47. CODE GENERATION: GIT IGNORE SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  48. !/src/services/ /src/services/ * * / / . . . !/src/services/telemetry/index.test.js

    !/src/services/translations/index.test.js !/src/services/validations/index.test.js / / . . . !/src/services/telemetry/ /src/services/telemetry/ * * !/src/services/translations/ /src/services/translations/ * * !/src/services/validations/ /src/services/validations/ * * / / . . . CODE GENERATION: GIT IGNORE
  49. try { const filePaths = await globby(includePattern); const files =

    normalizePaths("./", filePaths); files.forEach((file) = > { finalContents += `\n\n!/${file}`; }); } catch (err) { logger.error(err as string); } const directoriesPaths = getDirectoriesRecursive(baseDir); const directories = normalizePaths("./", directoriesPaths); directories.forEach((directory) = > { finalContents += `\n\n!/${directory}/`; finalContents += `\n/${directory}/ * * `; }); await outputFile(destGitignoreFile, finalContents); CODE GENERATION: GIT IGNORE
  50. WEBDRIVER 4 ใฑบ 7 SPEED AT SCALE: OPTIMIZING THE LARGEST

    CX PLATFORM OUT THERE / SHIPPING LESS CODE /
  51. WHICH BROWSERS ARE VISITING OUR APP? โ€”โ€‰APP DYNAMICS REPORT SPEED

    AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS
  52. โ† THERE'S NO NEED TO SEND POLYFILLS FOR FEATURES ALREADY

    SUPPORTED BY THE BROWSER. โ† THEY SHOULD BE DELIVERED ONLY WHEN NECESSARY, NOT PREEMPTIVELY. โ† ESSENTIAL FUNCTIONALITY MUST BE AVAILABLE TO PREVENT RUNTIME ERRORS. POLYFILLING: RECAP
  53. #1 of 3 SPEED AT SCALE: OPTIMIZING THE LARGEST CX

    PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES POLYFILL.IO
  54. polyfill.io โ† INSPECTS THE BROWSERโ€™S USER-AGENT AND SERVES A SCRIPT

    WITH POLYFILLS TARGETED SPECIFICALLY AT THAT BROWSER โ† A SINGLE SCRIPT IN FRONT OF YOUR BUNDLE โ† SUPPORTS PICKING A SUBSET OF POLYFILLS (E.G. Map AND Promise)
  55. polyfill.io THE SCRIPT IS HOSTED ON A DIFFERENT SERVER โ†

    THE BROWSER WILL HAVE TO SPEND EXTRA 50-300 MS TO SETUP A CONNECTION = EXTRA TIME TO INTERACTIVE โ† POLYFILL.IO OUTAGE = YOUR SITE WILL EITHER GET VERY SLOW, OR WILL BREAK IN OLDER BROWSERS
  56. #2 of 3 SPEED AT SCALE: OPTIMIZING THE LARGEST CX

    PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES BABEL PRESET
  57. useBuiltIns { "presets": [ ["env", { / / Specify browsers

    youโ€™re targeting . . . "targets": "> 0.25%, not dead", / / . . . and either . . . "useBuiltIns": "entry", / / . . . or "useBuiltIns": "usage" }] ] }
  58. โ† NOT VERY USEFUL IF YOUโ€™RE TARGETING OLD BROWSERS (E.G.

    IE 11) โ† IT MIGHT REMOVE SOME POLYFILLS, BUT MOST OF THEM WILL STAY IN THE BUNDLE AND WOULD STILL BE DOWNLOADED BY EVERYONE useBuiltIns: entry
  59. useBuiltIns: usage โ† IT DOESN'T ADD POLYFILLS FOR DEPENDENCIES* โ†

    YOU MIGHT GET RUNTIME ERRORS IN LEGACY BROWSERS โ† IT MIGHT ALSO ADD EXCESSIVE POLYFILLS, E.G. โ€ข Array.includes AND String.includes โ€ข Symbol.toStringTag, Math.toStringTag, ETC.
  60. useBuiltIns โ† โ€œENTRYโ€ MODE TO SAFELY REDUCE THE NUMBER OF

    POLYFILLS SENT. โ† โ€œUSAGEโ€ MODE TO UNSAFELY REDUCE FURTHER THE NUMBER OF POLYFILLS SENT.
  61. #3 of 3 SPEED AT SCALE: OPTIMIZING THE LARGEST CX

    PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES DIFFERENTIAL SERVING
  62. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / TARGETING DIFFERENT BROWSERS / STRATEGIES
  63. DIFFERENTIAL SERVING โ€” SMART BUNDLING: HOW TO SERVE LEGACY CODE

    ONLY TO LEGACY BROWSERS โ€ข SHUBHAM KANODIA
  64. #gotcha ๐Ÿคท To ship the right polyfills to the right

    users you have to send different code to different users.
  65. โ† YOU DON'T HAVE TO SHIP POLYFILLS โ† YOU DON'T

    HAVE TO TRANSPILE EVERYTHING TO ES5 โ€ข CLASSES: STILL CLASSES, NOT HUGE TEMPLATES โ€ข ASYNC/AWAIT/GENERATORS: KEEP THEIR FORM, NOT HUGE ES5-COMPATIBLE SWITCH-CASE-BASED STATE MACHINES CREATING TWO BUNDLESโ€ฆ
  66. < ! - - Full polyfill bundle for old browsers

    - - > <script nomodule src="/polyfills/full.min.js"></script> < ! - - Smaller polyfill bundle for browsers with ES2015+ support - - > <script type="module" src="/polyfills/modern.min.js"></script> < ! - - Bundle script. `defer` is required to execute this script after the `type="module"` one - - > <script src="/bundle.min.js" defer></script> SERVING TWO BUNDLESโ€ฆ
  67. โ† OLD BROWSERS โ€“ ONES THAT DONโ€™T SUPPORT ES2015 โ€“

    WILL NOT LOAD type="module" SCRIPTS โ€“ AND WILL LOAD nomodule ONES. โ† YOU CAN USE nomodule TO SERVE ES2015 POLYFILLS TO BROWSERS THAT NEED THEM. SERVING TWO BUNDLESโ€ฆ
  68. โ† SAFARI 10.1 SUPPORTS type="module" BUT DOESNโ€™T SUPPORT THE nomodule

    ATTRIBUTE. โ† DISTINGUISHES BETWEEN ES5 AND ES2015+ BROWSERS, BUT NEWER STANDARDS LIKE ES2016 BROUGHT MORE POLYFILLABLE FEATURES. THIS LEADS TO SERVING UNNECESSARY POLYFILLS TO MOST ES2015+ BROWSERS. โ† type="module" SCRIPTS ARE ALWAYS DEFERRED.* SERVING TWO BUNDLESโ€ฆ
  69. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / TARGETING DIFFERENT BROWSERS / STRATEGIES
  70. #1 of 3 RAW MODULE/NOMODULE โ€” WHAT IS DIFFERENTIAL SERVING

    โ€ข BY JOHN STEWART SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  71. #1 of 3 โ† DOWNLOADS BOTH BUNDLES AND EXECUTES BOTH

    BUNDLES โ† DOWNLOADS BOTH BUNDLES โ† DOWNLOADS LEGACY BUNDLE AND DOWNLOADS ESM BUNDLE TWICE RAW MODULE/NOMODULE โ€” WHAT IS DIFFERENTIAL SERVING? โ€ข JOHN STEWART SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  72. #2 of 3 USER AGENT DETECTION โ€” SMART BUNDLING โ€ข

    SHUBHAM KANODIA SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  73. #2 of 3 router.get('/', async (ctx, next) = > {

    const useragent = ctx.get('User-Agent') const isModernUser = matchesUA(useragent) const index = isModernUser ? 'modern/โ€ฆ', โ€˜legacy/โ€ฆ' await send(ctx, index); }); USER AGENT DETECTION SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  74. #2 of 3 โ† YOU MIGHT NOT BE IN CONTROL

    OF THE SERVER. โ† IF YOU LOAD A MODERN SCRIPT WITHOUT THE MODULE ATTRIBUTE, YOU LOSE STREAM PARSING AND OFF MAIN THREAD COMPILATION. USER AGENT DETECTION SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  75. #3 of 3 RUNTIME DETECTION <script> var script = document.createElement('script');

    var prefix = (!('noModule' in check)) ? "/ie11" : "/esm"; script.src = prefix + "/index.js"; document.head.appendChild(script); </script> SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / TARGETING DIFFERENT BROWSERS / STRATEGIES
  76. SUMMING UPโ€ฆ โ† 1โƒฃ POLYFILL.IO: EASY AND DOESNโ€™T SHIP ANYTHING

    TO MODERN BROWSERS. COSTLY IN FOR TTI AND FCP. โ† 2โƒฃ useBuiltIns: EASY TO SETUP BUT EITHER NOT VERY USEFUL FOR OLDER BROWSERS, OR REQUIRES YOU TO COMPLILE NODE_MODULES AS WELL. โ† 3โƒฃ module/nomodule: EASY AND WIDE SUPPORT BUT ONLY STRIPS ES2015โˆ’POLYFILLS.
  77. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / TARGETING DIFFERENT BROWSERS / FEATURE DETECTION
  78. <Suspense fallback={<div>Loading . . . </div>}> <With unsupported> <NetworkStatus networkInfo="unsupported"

    /> <Video /> </With> <With effectiveConnectionType="2g"> <NetworkStatus networkInfo="2g" /> <Preview /> </With> </Suspense> FEATURE DETECTION: NETWORK STATUS
  79. const Video = lazy(() = > import("./Video")); const Preview =

    lazy(() = > import("./Preview")); const networkInfo = useNetworkStatus(); const { With, Switch, Otherwise } = usePatternMatch(networkInfo); FEATURE DETECTION: NETWORK STATUS
  80. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / TARGETING DIFFERENT BROWSERS / FEATURE DETECTION
  81. โ† JAVASCRIPT: MAIN (หœ139.09 KB) + VENDOR (หœ50.85 KB) +

    POLYFILLS (หœ31 KB) โ† CSS: MAIN (หœ58.21 KB) โ† TOTAL: JAVASCRIPT (220.94 KB) + CSS (58.21 KB) = 279.15 KB BUNDLE FOOTPRINT: BEFORE
  82. BUNDLE FOOTPRINT: AFTER JAVASCRIPT (164.51 KB) + CSS (12.27 KB)

    = TOTAL (176.78 KB) MODERN JAVASCRIPT (210.83 KB) + CSS (12.27 KB) = TOTAL (223.1 KB) LEGACY
  83. BUNDLE FOOTPRINT: AFTER หœ37% SMALLER BUNDLE FOOTPRINT ON MODERN BROWSERS:

    279.15 KB โ†’ 176.78 KB MODERN หœ25% SMALLER BUNDLE FOOTPRINT ON LEGACY BROWSERS 279.15 KB โ†’ 223.1 KB LEGACY
  84. BUNDLE FOOTPRINT: AFTER BROWSERS THAT SUPPORT ES MODULES AND HAPPEN

    NOT TO NEED EXTRA POLYFILLS. AS OF OCT 6, 2023, THESE REPRESENT 95.72% OF THE GLOBAL USAGE. MODERN BROWSERS THAT DONโ€™T FIT THE PREVIOUS CRITERIA. MOSTLY THOSE ARE PRE-2018 BROWSERS WITH INTERNET EXPLORER BEING THE HIGHLIGHT. LEGACY
  85. BROWSER LINE = SUPPORT FOR THE NEW SMALLER BUNDLE =

    WILL DOWNLOAD THE LEGACY BUNDLE WITH POLYFILLS โ€” CANIUSE.COM
  86. AS WE CAN SEE, MOST OF THEM SIGNIFICANTLY IMPROVED AFTER

    ENABLING THE FEATURE FLAG: โ† FCP: 8.3S โ†’ 2.3S = 6S FASTER โ† SPEED INDEX: 8.3S โ†’ 4.8S = 3.5S FASTER โ† LCP: 8.8S โ†’ 6.4S = 2.4S FASTER โ† TTI: 8.7S โ†’ 6.7S = 2S FASTER CORE WEB VITALS: AFTER
  87. TIMINGS VISUALLY COMPLETE LAST VISUAL CHANGE LOAD TIME (ONLOAD) LOAD

    TIME (FULLY LOADED) DOM CONTENT LOADED FEATURE FLAG ON FEATURE FLAG OFF โ€” WEBPAGETEST
  88. #protip ๐Ÿ’ก Dynamic imports are a great tool but, like

    all optimizations, they donโ€™t come for free.
  89. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / ONGOING EFFORTS / PREACT IN PRODUCTION
  90. โ† WAY SMALLER BUNDLE (APPROXIMATELY 3.5KB) โ† FASTER VIRTUAL DOM

    IMPLEMENTATION โ† MORE EFFECTIVE MEMORY USAGE PREACT IN PRODUCTION
  91. โ† MANY PERFORMANCE OPTIMIZATIONS CAN BE MADE WHEN WE CAN

    PREDICT WHAT USERS MIGHT DO โ† RESOURCE HINTS ARE A SIMPLE BUT EFFECTIVE WAY TO ALLOW DEVELOPERS TO HELP THE BROWSER TO STAY ONE STEP AHEAD OF THE USER AND KEEP PAGES FAST RESOURCE HINTS
  92. RESOURCE HINTS < ! - - Preconnect - - >

    <link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://scripts.example.com"> < ! - - Preloading - - > <link rel="preload" href="https://example.com/fonts/font.woff"> < ! - - DNS Prefetch - - > <link rel="dns-prefetch" href="https://fonts.gstatic.com"> <link rel="dns-prefetch" href="https://images.example.com"> < ! - - Prefetch - - > <link rel="prefetch" href="/uploads/images/pic.png"> <link rel="prefetch" href="https://example.com/news/?page=2"> < ! - - Prerender - - > <link rel="prerender" href="https://example.com/news/?page=2">
  93. RESOURCE HINTS < ! - - Preconnect - - >

    <link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://scripts.example.com"> < ! - - Preloading - - > <link rel="preload" href="https://example.com/fonts/font.woff"> < ! - - DNS Prefetch - - > <link rel="dns-prefetch" href="https://fonts.gstatic.com"> <link rel="dns-prefetch" href="https://images.example.com"> < ! - - Prefetch - - > <link rel="prefetch" href="/uploads/images/pic.png"> <link rel="prefetch" href="https://example.com/news/?page=2"> < ! - - Prerender - - > <link rel="prerender" href="https://example.com/news/?page=2">
  94. โ† IT ALLOWS THE BROWSER TO SETUP EARLY CONNECTIONS BEFORE

    THE REQUEST IS ACTUALLY SENT TO THE SERVER. THIS INCLUDES: โ€ข TLS NEGOTIATIONS โ€ข TCP HANDSHAKES โ† ELIMINATES ROUNDTRIP LATENCY PRECONNECT
  95. PRECONNECT 100MS 200MS 300MS 400MS 500MS 600MS 700MS HTML CSS

    FONT 1 FONT 2 FONTS START LOADING FONTS RENDERED
  96. PRECONNECT 100MS 200MS 300MS 400MS 500MS 600MS 700MS HTML CSS

    FONT 1 FONT 2 FONTS START LOADING FONTS RENDERED FONT 1 FONT 2
  97. โ† DNS PREFETCH โ‡ข WARNS THE BROWSER ABOUT THE DOMAINS

    ITโ€™S GOING TO NEED TO LOOK UP โ† PREFETCH โ‡ข FETCH RESOURCES IN THE BACKGROUND AND STORE THEM IN CACHE โ† PRERENDER โ‡ข IT GOES ONE STEP FURTHER AND EXECUTES THE FILES โ€ฆAND MUCH MORE!
  98. โ† INCREASE THE PRIORITY OF THE LCP IMAGE โ† LOWER

    THE PRIORITY OF ABOVE-THE-FOLD IMAGES AND PRELOADED RESOURCES โ† LOWER THE PRIORITY FOR NON-CRITICAL DATA FETCHES โ† REPRIORITIZE SCRIPTS PRIORITY HINTS
  99. PRIORITY HINTS < ! - - Increase the priority of

    the LCP image - - > <img src="image.jpg" fetchpriority="high" /> < ! - - Lower the priority of above-the-fold images - - > <ul class="carousel"> <img src="img/carousel-1.jpg" fetchpriority="high" /> <img src="img/carousel-2.jpg" fetchpriority="low" /> <img src="img/carousel-3.jpg" fetchpriority="low" /> </ul> < ! - - Reprioritize scripts - - > <script src="async_but_important.js" async fetchpriority="high"></script> <script src="blocking_but_unimportant.js" fetchpriority="low"></script>
  100. PRIORITY HINTS < ! - - Increase the priority of

    the LCP image - - > <img src="image.jpg" fetchpriority="high" /> < ! - - Lower the priority of above-the-fold images - - > <ul class="carousel"> <img src="img/carousel-1.jpg" fetchpriority="high" /> <img src="img/carousel-2.jpg" fetchpriority="low" /> <img src="img/carousel-3.jpg" fetchpriority="low" /> </ul> < ! - - Reprioritize scripts - - > <script src="async_but_important.js" async fetchpriority="high"></script> <script src="blocking_but_unimportant.js" fetchpriority="low"></script>
  101. PRIORITY HINTS / / Important validation data const user =

    await fetch("/user"); / / Less important content data const relatedPosts = await fetch("/posts/suggested", { priority: "low" });
  102. PRIORITY HINTS / / Important validation data const user =

    await fetch("/user"); / / Less important content data const relatedPosts = await fetch("/posts/suggested", { priority: "low" });
  103. โ† CSS TRICKS (E.G. font-display: swap AND will- change) โ†

    AVOID THE INITIAL HTTP TO HTTPS REDIRECTS SO THAT PAGES LOAD FASTER USING HTTP STRICT TRANSPORT SECURITY (HSTS) โ† SEND DATA WITHOUT EXTRA ROUND TRIPS WITH QUIC FUTURE WORK
  104. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / FUTURE WORK / PREDICTIVE PREFETCHING
  105. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE

    / FUTURE WORK / PREDICTIVE PREFETCHING
  106. #research ๐Ÿ“š 40% of Brits reported that they had become

    physically violent toward their computers. โ€”โ€‰British Psychology Society, 2009
  107. #research ๐Ÿ“š A 500ms delay resulted in up to a

    26% increase in frustration and up to an 8% decrease in engagement. โ€”โ€‰Radware research, 2013
  108. #research ๐Ÿ“š Delayed web pages caused a 38% rise in

    mobile users' heart rates โ€” equivalent to the anxiety of watching a horror movie alone. โ€”โ€‰Ericsson ConsumerLab neuro research, 2015
  109. #research ๐Ÿ“š 53% of mobile users abandon sites that take

    over 3 seconds to load. โ€”โ€‰DoubleClick, 2020
  110. #1 of 6 YOU HAVE TO SHIP POLYFILLS TO ALL

    BROWSERS YOUR USERS MIGHT USEโ€ฆ . . . BUT IT'S A BAD IDEA TO SHIP ALL THEORETICALLY REQUIRED POLYFILLS TO ALL OF THEM! SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS
  111. #2 of 6 ALWAYS CORRELATE BUSINESS METRICS WITH PERFORMANCE THERE

    IS NO WAY TO GAME THESE BECAUSE THEY DEPEND ON THE BEHAVIOR OF REAL USERS. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS
  112. #3 of 6 DON'T TAKE FAST NETWORKS, CPUS AND RAM

    FOR GRANTED. TEST ON REAL PHONES AND NETWORKS. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS
  113. #4 of 6 GET RID OF THE NOISE IN YOUR

    METRICS. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS
  114. #4 of 6 GET RID OF THE NOISE IN YOUR

    METRICS. โ† DONโ€™T TRUST CLIENT TIMESTAMPS, ONLY DELTAS. โ† CHECK FOR A REASONABLE RATE OF DATA INPUT PER CLIENT. โ† SEGREGATE KNOWN TRAFFIC (WELL- BEHAVED BOTS). SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS
  115. #5 of 6 SPEED AT SCALE: OPTIMIZING THE LARGEST CX

    PLATFORM OUT THERE / CLOSING THOUGHTS UNDERSTANDING THESE INTERNALS AND THEIR RATIONALES HELPS US IMPLEMENT OUR OWN ABSTRACTIONS E.G. THE JSCODESHIFT-BASED UPGRADE TOOL.
  116. #6 of 6 THERE'S NO SILVER BULLET. IDENTIFY YOUR CORE

    METRICS. SPEED AT SCALE: OPTIMIZING THE LARGEST CX PLATFORM OUT THERE / CLOSING THOUGHTS