├── .editorconfig ├── .github └── workflows │ └── next.yaml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── components ├── bluesky.js ├── corner.js ├── footer.js ├── github.js ├── header.js ├── layout.js ├── linkedin.js └── twitter.js ├── context ├── courseInfoContext.js └── headerContext.js ├── course.json ├── csv └── index.js ├── data ├── copyCode.js ├── course.js └── lesson.js ├── lessons ├── 01-welcome │ ├── A-intro.md │ ├── B-my-setup.md │ └── C-react-19.md ├── 02-react-render-modes │ ├── A-client-side-react.md │ ├── B-static-site-generation.md │ ├── C-server-side-rendering.md │ └── meta.json ├── 03-rscs-without-a-framework │ ├── A-intro-to-react-server-components.md │ ├── B-rsc-dependencies.md │ ├── C-server-and-client-components.md │ ├── D-the-rsc-server.md │ └── meta.json ├── 04-rscs-with-nextjs │ ├── A-abbreviated-intro-to-nextjs.md │ ├── B-server-components.md │ ├── C-server-actions.md │ ├── D-server-and-client-components-together.md │ ├── E-limitations-of-rscs.md │ └── meta.json ├── 05-performance-optimizations │ ├── A-where-react-can-be-slow.md │ ├── B-the-project.md │ ├── C-fixing-it.md │ ├── D-other-perf-questions.md │ └── meta.json ├── 06-transitions │ ├── A-what-are-transitions.md │ ├── B-the-project.md │ ├── C-making-it-work-without-transitions.md │ ├── D-making-it-work-with-transitions.md │ └── meta.json ├── 07-optimistic-values │ ├── A-what-are-optimistic-values.md │ ├── B-the-project.md │ ├── C-useOptimistic-Hook.md │ └── meta.json ├── 08-deferred-values │ ├── A-what-are-deferred-values.md │ ├── B-the-project.md │ ├── C-making-it-less-janky.md │ └── meta.json └── 09-wrap-up │ ├── A-congrats.md │ └── meta.json ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── index.js └── lessons │ └── [section] │ └── [slug].js ├── public ├── .nojekyll └── images │ ├── apple-touch-icon.png │ ├── author.jpg │ ├── course-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── profile.png │ ├── request1.png │ ├── request2.png │ └── social-share-cover.jpg ├── styles ├── courses.css ├── footer.css └── variables.css └── summary ├── getPrompt.js └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 -------------------------------------------------------------------------------- /.github/workflows/next.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy NextJS Course Site to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: npm install, build 14 | run: | 15 | npm install 16 | npm run build 17 | - name: Deploy site to gh-pages branch 18 | uses: crazy-max/ghaction-github-pages@v2 19 | with: 20 | target_branch: gh-pages 21 | build_dir: out 22 | fqdn: intermediate-react-v6.holt.courses 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | *.csv 33 | 34 | .env 35 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Intermediate React, v6 Course 2 | 3 | This is a companion repository for the [Intermediate React, v6](https://frontendmasters.com/courses/intermediate-react-v6/) course on Frontend Masters. 4 | [![Frontend Masters](https://static.frontendmasters.com/assets/brand/logos/full.png)](https://frontendmasters.com/courses/intermediate-react-v6/) 5 | 6 | View the [course website](https://intermediate-react-v6.holt.courses/) 7 | -------------------------------------------------------------------------------- /components/bluesky.js: -------------------------------------------------------------------------------- 1 | export default function BlueskyLogo() { 2 | return ( 3 | 9 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/corner.js: -------------------------------------------------------------------------------- 1 | export default function Corner() { 2 | return ( 3 |
4 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Gh from "./github"; 3 | import Tw from "./twitter"; 4 | import Li from "./linkedin"; 5 | import Bs from "./bluesky"; 6 | 7 | export default function Footer({ twitter, linkedin, github, bluesky }) { 8 | return ( 9 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /components/github.js: -------------------------------------------------------------------------------- 1 | export default function GitHub() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import Link from "next/link"; 3 | import { Context as HeaderContext } from "../context/headerContext"; 4 | import { Context as CourseContext } from "../context/courseInfoContext"; 5 | 6 | export default function Header(props) { 7 | const [{ section, title, icon }] = useContext(HeaderContext); 8 | const { frontendMastersLink } = useContext(CourseContext); 9 | return ( 10 |
11 |

12 | {props.title} 13 |

14 |
15 | {frontendMastersLink ? ( 16 | 17 | Watch on Frontend Masters 18 | 19 | ) : null} 20 | {section ? ( 21 |

22 | {section} {title} 23 |

24 | ) : null} 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import Footer from "./footer"; 4 | import Header from "./header"; 5 | import getCourseConfig from "../data/course"; 6 | import { Provider as HeaderProvider } from "../context/headerContext"; 7 | import { Provider as CourseInfoProvider } from "../context/courseInfoContext"; 8 | 9 | function Layout({ children }) { 10 | const courseInfo = getCourseConfig(); 11 | const headerHook = useState({}); 12 | return ( 13 | 14 | 15 |
16 |
17 |
18 |
{children}
19 |
20 | 21 | 28 |
34 |
35 |
36 |
37 | ); 38 | } 39 | 40 | export default function App({ children }) { 41 | return {children}; 42 | } 43 | -------------------------------------------------------------------------------- /components/linkedin.js: -------------------------------------------------------------------------------- 1 | export default function LinkedIn() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/twitter.js: -------------------------------------------------------------------------------- 1 | export default function Twitter() { 2 | return ( 3 | 10 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /context/courseInfoContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const courseInfoContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = courseInfoContext.Provider; 6 | export const Consumer = courseInfoContext.Consumer; 7 | export const Context = courseInfoContext; 8 | -------------------------------------------------------------------------------- /context/headerContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const headerContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = headerContext.Provider; 6 | export const Consumer = headerContext.Consumer; 7 | export const Context = headerContext; 8 | -------------------------------------------------------------------------------- /course.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Brian Holt", 4 | "company": "Neon" 5 | }, 6 | "title": "Intermediate React v6", 7 | "subtitle": "As taught for Frontend Masters", 8 | "frontendMastersLink": "https://frontendmasters.com/courses/intermediate-react-v6/", 9 | "social": { 10 | "linkedin": "btholt", 11 | "github": "btholt", 12 | "twitter": "holtbt", 13 | "bluesky": "brianholt.me" 14 | }, 15 | "description": "Learn intermediate and advance concepts like React Server Components for React as taught for Frontend Masters by Brian Holt.", 16 | "keywords": [ 17 | "React", 18 | "Frontend Masters", 19 | "Brian Holt", 20 | "RSC", 21 | "server components", 22 | "javascript", 23 | "framework", 24 | "web development" 25 | ], 26 | "csvPath": "./out/lessons.csv" 27 | } 28 | -------------------------------------------------------------------------------- /csv/index.js: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import path from "path"; 3 | import { convertArrayToCSV } from "convert-array-to-csv"; 4 | import { getLessons } from "../data/lesson.js"; 5 | 6 | async function start() { 7 | const configBuffer = await fs.readFile( 8 | path.join(process.cwd(), "course.json") 9 | ); 10 | const config = JSON.parse(configBuffer); 11 | 12 | if (!config.csvPath) { 13 | console.log("no csvPath in course.json, skipping CSV generation"); 14 | return; 15 | } 16 | 17 | process.env.BASE_URL = config?.productionBaseUrl || ""; 18 | const sections = await getLessons(); 19 | 20 | const lessons = []; 21 | 22 | for (let i = 0; i < sections.length; i++) { 23 | const section = sections[i]; 24 | 25 | for (let j = 0; j < section.lessons.length; j++) { 26 | const lesson = section.lessons[j]; 27 | 28 | lessons.push({ 29 | order: lesson.order, 30 | sectionTitle: section.title, 31 | lessonTitle: lesson.title, 32 | slug: section.slug + "/" + lesson.slug, 33 | sectionIcon: section.icon, 34 | filePath: lesson.fullSlug, 35 | description: lesson.description, 36 | }); 37 | } 38 | } 39 | 40 | const csv = convertArrayToCSV(lessons); 41 | 42 | await fs.writeFile(config.csvPath, csv); 43 | console.log(`wrote ${lessons.length} rows to ${config.csvPath}`); 44 | } 45 | 46 | start(); 47 | -------------------------------------------------------------------------------- /data/copyCode.js: -------------------------------------------------------------------------------- 1 | function createDOMElements() { 2 | const container = document.createElement("div"); 3 | container.innerHTML = 4 | "
"; 5 | container.className = "div-copy"; 6 | return container; 7 | } 8 | 9 | function attachCopyCodeFunctionality(div) { 10 | const elementsToClean = []; 11 | document 12 | .querySelectorAll("pre") 13 | .forEach(function createButtonAndAttachHandlers(pre) { 14 | let timeout = null; 15 | const copy = div.cloneNode(true); 16 | pre.appendChild(copy); 17 | elementsToClean.push(pre); 18 | 19 | copy.onclick = function copyTextToClipboard() { 20 | navigator.clipboard.writeText(pre.textContent); 21 | copy.classList.add("clicked"); 22 | clearTimeout(timeout); 23 | timeout = setTimeout(function hidePopup() { 24 | copy.classList.remove("clicked"); 25 | }, 1500); 26 | }; 27 | }); 28 | 29 | return elementsToClean; 30 | } 31 | 32 | export default function createCopyCodeFunctionality() { 33 | const container = createDOMElements(); 34 | return attachCopyCodeFunctionality(container); 35 | } 36 | -------------------------------------------------------------------------------- /data/course.js: -------------------------------------------------------------------------------- 1 | import config from "../course.json"; 2 | 3 | const DEFAULT_CONFIG = { 4 | author: { 5 | name: "An Author", 6 | company: "An Author's Company", 7 | }, 8 | title: "A Superb Course", 9 | subtitle: "That Teaches Nice Things", 10 | frontendMastersLink: "", 11 | description: "A nice course for nice people.", 12 | keywords: ["a nice course", "for people", "to learn", "nice things"], 13 | social: { 14 | linkedin: "btholt", 15 | github: "btholt", 16 | twitter: "holtbt", 17 | bluesky: "brianholt.me", 18 | }, 19 | productionBaseUrl: "/", 20 | }; 21 | 22 | export default function getCourseConfig() { 23 | return Object.assign({}, DEFAULT_CONFIG, config); 24 | } 25 | -------------------------------------------------------------------------------- /data/lesson.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs/promises"; 3 | import matter from "gray-matter"; 4 | import { titleCase } from "title-case"; 5 | import { Marked } from "marked"; 6 | import { markedHighlight } from "marked-highlight"; 7 | import hljs from "highlight.js"; 8 | 9 | const DEFAULT_ICON = "info-circle"; 10 | const lessonsPath = path.join(process.cwd(), "lessons"); 11 | 12 | const marked = new Marked( 13 | markedHighlight({ 14 | baseUrl: process.env.BASE_URL ? process.env.BASE_URL + "/" : "/", 15 | highlight: function (code, lang) { 16 | const language = hljs.getLanguage(lang) ? lang : "plaintext"; 17 | return hljs.highlight(code, { language }).value; 18 | }, 19 | langPrefix: "hljs language-", 20 | }) 21 | ); 22 | 23 | function getTitle(slug, override) { 24 | let title = override; 25 | if (!title) { 26 | title = titleCase(slug.split("-").join(" ")); 27 | } 28 | 29 | return title; 30 | } 31 | 32 | async function getMeta(section) { 33 | let meta = {}; 34 | try { 35 | const file = await fs.readFile( 36 | path.join(lessonsPath, section, "meta.json") 37 | ); 38 | meta = JSON.parse(file.toString()); 39 | } catch (e) { 40 | // no meta.json, nothing to do 41 | } 42 | 43 | return meta; 44 | } 45 | 46 | function slugify(inputPath) { 47 | const pathParts = inputPath.split("-"); 48 | const pathOrder = pathParts.shift(); 49 | const pathSlug = pathParts.join("-"); 50 | return { 51 | slug: pathSlug, 52 | order: pathOrder, 53 | title: titleCase(pathParts.join(" ")), 54 | }; 55 | } 56 | 57 | export async function getLessons() { 58 | const dir = await fs.readdir(lessonsPath); 59 | const sections = []; 60 | 61 | for (let dirFilename of dir) { 62 | const dirStats = await fs.lstat(path.join(lessonsPath, dirFilename)); 63 | 64 | if (dirStats.isFile()) { 65 | continue; 66 | } 67 | 68 | const lessonsDir = await fs.readdir(path.join(lessonsPath, dirFilename)); 69 | 70 | let { 71 | title: sectionTitle, 72 | order: sectionOrder, 73 | slug: sectionSlug, 74 | } = slugify(dirFilename); 75 | 76 | let icon = DEFAULT_ICON; 77 | 78 | const meta = await getMeta(dirFilename); 79 | if (meta.title) { 80 | sectionTitle = meta.title; 81 | } 82 | if (meta.icon) { 83 | icon = meta.icon; 84 | } 85 | 86 | const lessons = []; 87 | for (let lessonFilename of lessonsDir) { 88 | if (lessonFilename.slice(-3) !== ".md") { 89 | continue; 90 | } 91 | 92 | const filePath = path.join(lessonsPath, dirFilename, lessonFilename); 93 | 94 | const file = await fs.readFile(filePath); 95 | const { data } = matter(file.toString()); 96 | let slug = lessonFilename.replace(/\.md$/, ""); 97 | 98 | const slugParts = slug.split("-"); 99 | const lessonOrder = slugParts.shift(); 100 | 101 | slug = slugParts.join("-"); 102 | 103 | const title = getTitle(slug, data.title); 104 | 105 | lessons.push({ 106 | slug, 107 | fullSlug: `/lessons/${sectionSlug}/${slug}`, 108 | title, 109 | order: `${sectionOrder}${lessonOrder.toUpperCase()}`, 110 | path: filePath, 111 | description: data.description ? data.description : "", 112 | }); 113 | } 114 | 115 | sections.push({ 116 | icon, 117 | title: sectionTitle, 118 | slug: sectionSlug, 119 | lessons, 120 | order: sectionOrder, 121 | }); 122 | } 123 | 124 | return sections; 125 | } 126 | 127 | export async function getLesson(targetDir, targetFile) { 128 | const dir = await fs.readdir(lessonsPath); 129 | 130 | for (let i = 0; i < dir.length; i++) { 131 | const dirPath = dir[i]; 132 | if (dirPath.endsWith(targetDir)) { 133 | const lessonDir = ( 134 | await fs.readdir(path.join(lessonsPath, dirPath)) 135 | ).filter((str) => str.endsWith(".md")); 136 | 137 | for (let j = 0; j < lessonDir.length; j++) { 138 | const slugPath = lessonDir[j]; 139 | if (slugPath.endsWith(targetFile + ".md")) { 140 | const filePath = path.join(lessonsPath, dirPath, slugPath); 141 | const file = await fs.readFile(filePath); 142 | const { data, content } = matter(file.toString()); 143 | const html = marked.parse(content); 144 | const title = getTitle(targetFile, data.title); 145 | const meta = await getMeta(dirPath); 146 | 147 | const section = getTitle(targetDir, meta.title); 148 | const icon = meta.icon ? meta.icon : DEFAULT_ICON; 149 | 150 | let nextSlug; 151 | let prevSlug; 152 | 153 | // get next 154 | if (lessonDir[j + 1]) { 155 | // has next in section 156 | const { slug: next } = slugify(lessonDir[j + 1]); 157 | nextSlug = `${targetDir}/${next.replace(/\.md$/, "")}`; 158 | } else if (dir[i + 1]) { 159 | // has next in next section 160 | const nextDir = ( 161 | await fs.readdir(path.join(lessonsPath, dir[i + 1])) 162 | ).filter((str) => str.endsWith(".md")); 163 | const nextDirSlug = slugify(dir[i + 1]).slug; 164 | const nextLessonSlug = slugify(nextDir[0]).slug.replace( 165 | /\.md$/, 166 | "" 167 | ); 168 | nextSlug = `${nextDirSlug}/${nextLessonSlug}`; 169 | } else { 170 | // last section 171 | nextSlug = null; 172 | } 173 | 174 | // get prev 175 | if (lessonDir[j - 1]) { 176 | // has prev in section 177 | const { slug: prev } = slugify(lessonDir[j - 1]); 178 | prevSlug = `${targetDir}/${prev.replace(/\.md$/, "")}`; 179 | } else if (dir[i - 1]) { 180 | // has prev in prev section 181 | const prevDir = ( 182 | await fs.readdir(path.join(lessonsPath, dir[i - 1])) 183 | ).filter((str) => str.endsWith(".md")); 184 | const prevDirSlug = slugify(dir[i - 1]).slug; 185 | const prevLessonSlug = slugify( 186 | prevDir[prevDir.length - 1] 187 | ).slug.replace(/\.md$/, ""); 188 | prevSlug = `${prevDirSlug}/${prevLessonSlug}`; 189 | } else { 190 | // first section 191 | prevSlug = null; 192 | } 193 | 194 | const base = process.env.BASE_URL ? process.env.BASE_URL : "/"; 195 | 196 | return { 197 | attributes: data, 198 | html, 199 | markdown: content, 200 | slug: targetFile, 201 | title, 202 | section, 203 | icon, 204 | filePath, 205 | nextSlug: nextSlug ? path.join(base, "lessons", nextSlug) : null, 206 | prevSlug: prevSlug ? path.join(base, "lessons", prevSlug) : null, 207 | }; 208 | } 209 | } 210 | } 211 | } 212 | 213 | return false; 214 | } 215 | -------------------------------------------------------------------------------- /lessons/01-welcome/A-intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Join Brian Holt in 'Intermediate React v6' for Frontend Masters to gain 4 | expertise in server-side and performance capabilities of React. This 5 | comprehensive course requires prior knowledge from 'The Complete Intro to 6 | React v9' and focuses on leveraging React's inherent tools for web 7 | development, making it ideal for anyone with a foundational understanding of 8 | React. Brian Holt, an experienced React developer, provides hands-on projects 9 | and detailed instructions, enhancing your React skills and knowledge. 10 | keywords: 11 | - Intermediate React 12 | - Frontend Masters 13 | - Brian Holt 14 | - React Server Components 15 | - JavaScript 16 | --- 17 | 18 | > 🚨 You do not need to watch/read previous versions of this course, Intermediate React. This is just the sixth revision of the course. You **do** need to have taken [the Complete Intro to React v9][v9] 19 | 20 | Hello! And welcome to the sixth revision of Intermediate React as taught for Frontend Masters! This is a total rewrite of the Intermediate curriculum and I am so excited for you to take it. Previous versions of this course focused on the ecosystem around React and [v5][v5] of this course is still valid for that: Tailwind, TypeScript, Redux, and other things. Beyond that, Frontend Masters has individual courses on all of those things as taught by other instructions. [Check out the learning path][path] to find all of those. 21 | 22 | This course logically be divided into two halves: server-side React and performance with React. Both halves are going to focus on the capabilities of React itself rather than tools around it. At the end of the course, you are going to understand all of the tools available to you to write React apps. There are some more APIs that we don't cover but they're so niche that they're not even worth coverings - mostly for library authors. 23 | 24 | ## Who is this course for? 25 | 26 | You! This course is for anyone looking to get more experience in React or deepen their expertise in it. No experience is necessary beyond the intro course. However, I guarantee anyone who is interested in React will learn something in here. This probably shouldn't be your first programming or JavaScript course, but beyond that, it should be comfortable for anyone. We do use some Node.js to run our API and for the build tools but extensive Node.js experience is not necessary, I'll give you the commands necessary to run it and cover the Node.js we do use. 27 | 28 | ## Who am I? 29 | 30 | ![Brian teaching](/images/social-share-cover.jpg) 31 | 32 | My name is Brian Holt and I've been writing React for a long time. I shipped Reddit's first React code in 2014. 33 | 34 |

We shipped reddit's first production @reactjs code last week, our checkout process.https://t.co/KUInwsCmAF

— Brian Holt (@holtbt) July 28, 2014
35 | 36 | I went on to write React at a variety of large companies and it's been my goto tool for over a decade at this point. It's safe to say it's been a defining feature in my career. 37 | 38 | I currently work as a staff product manager at [Neon][neon] working on developer tools and developer experience. I loved working on dev tools and dev experiences so much that I ended up working in tools and strategy. Previous to Neon, I've worked at Snowflake, Microsoft, LinkedIn, Netflix, Reddit, and some other startups. I've done everything from VP of product to dev rel to staff engineering tech lead. 39 | 40 | When I'm not working or teaching, you'll find me hanging out with my wife, son, and my soon-to-arrive daughter! I've lived the past six years in Seattle but I've just moved to Sacramento to get a bit more sun and to live close to amazing snowboarding in Tahoe! 🏂 I also enjoy hazy IPAs, Islay Scotches, road cycling, and playing Marvel Rivals poorly. 41 | 42 | Please catch up with me on social media! Be aware that I'm awful at responding to DMs!! 43 | 44 | - [𝕏][x] 45 | - [Bluesky][bs] 46 | - [LinkedIn][li] 47 | - [GitHub][gh] 48 | 49 | ## Where to File Issues 50 | 51 | I write these courses and take care to not make mistakes. However when teaching over ten hours of material, mistakes are inevitable, both here in the grammar and in the course with the material. However I (and the wonderful team at Frontend Masters) are constantly correcting the mistakes so that those of you that come later get the best product possible. If you find a mistake we'd love to fix it. The best way to do this is to [open a pull request or file an issue on the GitHub repo][issues]. While I'm always happy to chat and give advice on social media, I can't be tech support for everyone. And if you file it on GitHub, those who come later can Google the same answer you got. 52 | 53 | ## How the repo works 54 | 55 | There are two repos for this class: [the website you're currently on][site] and [the example projects][projects]. To get set up, clone or [download][zip] the projects repo: 56 | 57 | ```bash 58 | git clone https://github.com/btholt/irv6-project.git 59 | ``` 60 | 61 | We are going to work on a few separate projects. The first few are going to be written from scratch (except a little SQLite database and a CSS file, we'll talk about those when we get there.) So those don't have incomplete projects to start with, we'll just start from nothing. 62 | 63 | For the following ones, they have empty starter projects because I don't want to set up Vite a million times. We'll have you just open the repo to the right folder and start from there. Again, we'll have instructions on each lesson as we go. 64 | 65 | > And one last request! [Please star this repo][site]. It helps the course be more discoverable and with my fragile ego. 66 | 67 | [x]: https://twitter.com/holtbt 68 | [bs]: https://bsky.app/profile/brianholt.me 69 | [li]: https://www.linkedin.com/in/btholt/ 70 | [gh]: https://github.com/btholt 71 | [site]: https://github.com/btholt/intermediate-react-v6 72 | [projects]: https://github.com/btholt/irv6-project 73 | [issues]: https://github.com/btholt/irv6-project/issues 74 | [neon]: https://neon.tech/ 75 | [zip]: https://github.com/btholt/irv6-project/archive/refs/heads/main.zip 76 | [v9]: https://frontendmasters.com/courses/complete-react-v9/ 77 | [v5]: https://frontendmasters.com/courses/intermediate-react-v5/ 78 | [path]: https://frontendmasters.com/learn/react/ 79 | -------------------------------------------------------------------------------- /lessons/01-welcome/B-my-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Explore Brian Holt's 'Intermediate React v6' course, providing insights on the 4 | use of Node.js, preferred development tools like Visual Studio Code, and a 5 | balanced perspective on AI integration in coding. Learn how these tools and 6 | technologies enhance the process of writing efficient React Server Components, 7 | as taught for Frontend Masters. 8 | keywords: 9 | - React 10 | - Node.js 11 | - Visual Studio Code 12 | - AI in coding 13 | - Brian Holt 14 | --- 15 | 16 | ## Node.js 17 | 18 | You'll need to have a Node.js version installed, and preferably something after v20.16. I wrote this course with 22.14 but it should be fairly future-proof. 19 | 20 | I use [fnm][fnm] to manage my Node.js versions (similar to nvm). 21 | 22 | I _think_ this course would work with recent versions of [bun][bun] but it's untested. Beware if you decide go down this path. 23 | 24 | ## Tools FAQ 25 | 26 | ### What tools are your using? 27 | 28 | - Visual Studio Code – I used to work at Microsoft on VS Code so it's no surprise that I'll be using it in this course. We'll also be using a few extensions that I'll call out as we get there. 29 | - I frequently use Copilot and Cursor too, but I'll be disabling it for this so we can go through together, keystroke by keystroke. 30 | - Firefox – I want more than Chromium to exist so I support Firefox where I can. Feel free to use any browser; it won't matter in this course. 31 | - Terminal.app – I used to use iTerm2 and Hyper but in the end I appreciate how fast the default terminal is. 32 | 33 | ### What are you using? 34 | 35 | - Visual Studio Code 36 | - Dark+ Theme – It comes installed by default but it's not the default theme anymore. I'm so used to it that I can't switch. 37 | - [MonoLisa][monolisa] font – I like fonts and I look at it all day so I was okay paying for it. I have [ligatures][ligatures] enabled which is why you might see strange glyphs. If you want ligatures but don't want to pay, the linked ligature article has a few. I like Cascadia Code from Microsoft. 38 | - [vscode-icons][vscode-icons] – Lots of neat icons for VS Code and it's free. 39 | - Terminal 40 | - zsh – It comes with macOS now and I'm _way_ too lazy to switch back to bash. 41 | - [Dracula theme][dracula] – I like the pastels. I would use it in VS Code too if Dark+ wasn't ingrained in my blood. 42 | - [Starship Prompt][starship] – Very cool prompt that's just pretty. Also shows you what sort of project you're in which is occasionally useful 43 | - [Caskaydia Cove Nerd Font][nerd] – This works with Starship prompt to give you the JS logos and all those extra glyphs. It's based on Cascadia Code. 44 | 45 | ## A note on the use of AI in this course 46 | 47 | This course was written by me. I used AI in a few places for assistance, but every piece of code and every line of text on this site was written by me. The only places that it's actually AI written directly are the example rows of the database and the sample markdown - just filler content that I prompted Claude for. I do let OpenAI write all the SEO for these webpages (like all the OpenGraph keywords and descriptions.) 48 | 49 | My stance on AI is two-fold. In the hands of an experienced wielder AI is incredibly powerful. When I say experienced, I mean it in two ways: 1. experienced in writing the sort of code they're prompting for and 2. experienced in the usage of how to prompt and use AI well. In our case, AI will allow you to amplify the rate you can write React code, but that's only possible if you understand React. Sure, you can "vibe" code React with a very shallow understanding, but you're going to build tall houses of cards. You'll be better served by turning off the AI and handwriting code, understanding each precept, and then using that knowledge to co-write solid React with your AI toolkit. 50 | 51 | Do I use AI? Yes, it helps me work faster. But like a manager reviewing their interns' code, I am responsible for what I decide to ship, and so I keep a tight rein on it. 52 | 53 | [ligatures]: https://worldofzero.com/posts/enable-font-ligatures-vscode/ 54 | [monolisa]: https://www.monolisa.dev/ 55 | [vscode-icons]: https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons 56 | [dracula]: https://draculatheme.com/terminal 57 | [starship]: https://starship.rs/ 58 | [nerd]: https://www.nerdfonts.com/font-downloads 59 | [fnm]: https://github.com/Schniz/fnm 60 | [bun]: https://bun.sh/ 61 | -------------------------------------------------------------------------------- /lessons/01-welcome/C-react-19.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Explore the new features of React 19, focusing on React Server Components, 4 | which enable partial server-side rendering of React components, a feature that 5 | facilitates safe database querying directly within React. Discover insights 6 | into this cutting-edge technology, stable since 2023 and actively developed 7 | since 2022, with guidance from expert Brian Holt. Ideal for developers aiming 8 | to leverage the latest advancements in React functionality. 9 | keywords: 10 | - React 19 11 | - React Server Components 12 | - Brian Holt 13 | - web development 14 | - JavaScript 15 | - server-side rendering 16 | --- 17 | Let's talk about [React 19][19]. 18 | 19 | It came out at the tail end of 2024 officially but really had publicly been worked on [since late 2022][rfc]. So it's been worked nearly 3 years and been stable and in production (via Next.js as well as internally at Facebook) since [late 2023][next]. 20 | 21 | All of this to say: it's fairly new to _stable_ React but it's been worked on and shipped via "unstable" channels for nearly 2+ years. We've had a lot of time to learn some good and bad patterns, and it's going to be fun to learn them together with you. 22 | 23 | Primarily we are going to concern ourselves with React Server Components. This is a new way of writing React components that allows your React components to render partially on the server and then to be served in a client side app in React. It's very cool; it allows you to do things like safely directly query your database from within React, something we couldn't (or at least shouldn't) do previously. 24 | 25 | The Complete Intro course does some minor cover of version 19 features and the good news is nothing changed in what I taught - everything I taught stayed stable so no corrections since version was released after v9 of my intro course! 26 | 27 | [19]: https://react.dev/blog/2024/12/05/react-19 28 | [rfc]: https://github.com/reactjs/rfcs/pull/188 29 | [next]: https://nextjs.org/blog/next-14 30 | -------------------------------------------------------------------------------- /lessons/02-react-render-modes/A-client-side-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Client-Side React 3 | description: >- 4 | Learn about React Server Components in the 'Intermediate React v6' course by 5 | Brian Holt on Frontend Masters. Understand the trade-offs of React Server 6 | Components and different React rendering methods, including client-side, 7 | server-side, and static site generation. Explore the benefits and limitations 8 | of these approaches and optimize your web development skills using advanced 9 | React techniques. 10 | keywords: 11 | - React 12 | - Server Components 13 | - Brian Holt 14 | - Frontend Masters 15 | - web development 16 | --- 17 | 18 | > 💡 This course assumes you have taken or at least understand the concepts in [The Complete Intro to React v9][v9]. If you don't, please go back and take that one first, this will make a lot more sense. 19 | 20 | React Server Components (which I will now abbreviate as RSC) are a concept that have been teased for a few years before landing in stable React in early 2025 when React 19 was officially released. No doubt they are a very interesting piece of technology and offer a lot of upside. They have some interesting trade-offs and we'll talk about them, but don't be quick to paint them as bad as some have, and definitely don't use them _everywhere_ either, they have a time and place. 21 | 22 | Before we dive right into what an RSC is, it's helpful to understand _three_ other ways React renders your app. 23 | 24 | ## Client-side React 25 | 26 | This is just normal React, what your learned in the Complete Intro. Your server does nothing for you; it just sends the client browser an empty HTML page and a full bundle of JS to render your app. All React components are rendered on the client - your server does not help. 27 | 28 | This is how we've written React for a very long time (10+ years now) and will continue writing React for a long time yet this way. 29 | 30 | Brian's (not-so) 🔥 Take: this is the default way you should write React. Everything is a contextual performance enhancement, and should only be reached for when: 1. the performance enhancement actually meaningfully helps (it frequently doesn't) and 2. you actually have a need for the performance enhancement (you frequently don't). This style of rendering yields the simplest apps that are easiest to write, easiest to maintain, and easiest to debug. 31 | 32 | We're not going to write a client-side React app today – [Complete Intro to React][v9] covers that in-depth. So let's move on to writing SSG and SSR apps. 33 | 34 | [v9]: https://react-v9.holt.courses 35 | -------------------------------------------------------------------------------- /lessons/02-react-render-modes/B-static-site-generation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to implement Static Site Generation (SSG) using React and Next.js, 4 | with expert guidance from Brian Holt in the Intermediate React v6 course at 5 | Frontend Masters. Discover the simplicity of generating static web pages for 6 | content-heavy sites and explore how this streamlined approach can expedite 7 | course creation without sacrificing quality. Enhance your knowledge on using 8 | modern frameworks like Astro and Next.js for optimized static content 9 | delivery. 10 | keywords: 11 | - Static Site Generation 12 | - Next.js 13 | - React 14 | - Brian Holt 15 | - Frontend Masters 16 | --- 17 | > Fun fact: this very website is rendered via SSG with Next.js! [See the code here][code] 18 | 19 | Static Site generation allows you to use React to generate a fully static site. This is perfect for sites like blogs or course materials (like this site) where it's just a bunch of static pages that don't need the interactivity of React. It's enough to render this out once - it doesn't need the interactivity. The result is the end user is just shipped fully rendered flat HTML files - it doesn't need a server or client to do it. The developer can still add some interactivity via React, but minimally so. 20 | 21 | This is just useful for static content. I love it because it helps me ship these courses faster, but it's not a good fit for anything with much more than basic interactivity. 22 | 23 | ## Let's Build It 24 | 25 | This is going to be almost insultingly easy but I want you to see that at its core, they frameworks are mostly doing dev experience work and tools around the actual static site generation and that React itself is doing the heavy lifting. 26 | 27 | Create a new folder. In that folder, run 28 | 29 | ```bash 30 | npm init -y 31 | npm i react react-dom 32 | ``` 33 | 34 | In your package.json, add `"type": "module"` as a top level item. Also add `"build": "node ./build.js"` to your scripts. 35 | 36 | > 💡 We're going to do this with vanilla JS and not JSX so we don't need to bring in Babel/Vite/Webpack/whatever. You could absolutely bring in those if you wanted to, I'm just trying to keep these examples as simple as possible. 37 | 38 | Create index.html, put this in there: 39 | 40 | ```javascript 41 | 42 | 43 | 44 | SSG Example 45 | 46 | 47 |
48 | 49 | 50 | ``` 51 | 52 | Create an App.js file, put in there 53 | 54 | ```javascript 55 | import { createElement as h } from "react"; 56 | 57 | function App() { 58 | return h( 59 | "div", 60 | null, 61 | h("h1", null, "Hello Frontend Masters"), 62 | h("p", null, "This is SSG") 63 | ); 64 | } 65 | 66 | export default App; 67 | ``` 68 | 69 | Now create a build.js and put 70 | 71 | ```javascript 72 | import { renderToStaticMarkup } from "react-dom/server"; 73 | import { createElement as h } from "react"; 74 | import { 75 | readFileSync, 76 | writeFileSync, 77 | existsSync, 78 | mkdirSync, 79 | readdirSync, 80 | unlinkSync, 81 | } from "node:fs"; 82 | import { fileURLToPath } from "node:url"; 83 | import path, { dirname } from "node:path"; 84 | import App from "./App.js"; 85 | 86 | const __filename = fileURLToPath(import.meta.url); 87 | const __dirname = dirname(__filename); 88 | const distPath = path.join(__dirname, "dist"); 89 | 90 | const shell = readFileSync(path.join(__dirname, "index.html"), "utf8"); 91 | 92 | const app = renderToStaticMarkup(h(App)); 93 | const html = shell.replace("", app); 94 | 95 | // Create dist folder if it doesn't exist 96 | if (!existsSync(distPath)) { 97 | mkdirSync(distPath); 98 | } else { 99 | // Delete all files in dist folder if it exists 100 | const files = readdirSync(distPath); 101 | for (const file of files) { 102 | unlinkSync(path.join(distPath, file)); 103 | } 104 | } 105 | 106 | writeFileSync(path.join(distPath, "index.html"), html); 107 | ``` 108 | 109 | - Most of the code is making sure the dist folder exists, and if it does, is empty 110 | - We just take our App, render it to string, and stick it in the HTML shell 111 | - You actually _could_ just have React render everything, ``, `` and all. 112 | - We didn't include any CSS nor JS, but obviously you could. 113 | - For mild amounts of interactivity, you could include JS file with the React run time and hydrate the app, but we don't need to. 114 | - We're using renderToStaticMarkup - this very similar to the renderToString function but doesn't include any hints for React to hydrate later for SSR (server-side rendering, our next lesson). renderToString would work, it'll just include superfluous React stuff we don't need. 115 | 116 | This generates one page. You could have it generate many pages any number of ways. We could write a `routes.js` file that defines a route and its route component and then have build.js loop over that. We could use `react-router` or something like and then make our build.js use those routes to generate route. Any of these are viable. 117 | 118 | ## Okay but just use Astro, Next.js, Gatsby, etc. 119 | 120 | I wanted you to see how simple this is, but in reality I can't fathom why you'd do this. You are just `npm create astro@latest` from a full-featured framework that is optimized around making static content sites and I can't see why you wouldn't do that. This site is built on Next.js because it's the one I knew the best but if I were to rebuild my course builder website I'd use Astro. 121 | 122 | ## SSG with Next.js 123 | 124 | This site is entirely SSG'd. In general you will never directly write SSG code yourself; you will rely on a framework to do it for you. I would say the two primary ways to do this are either using [Next.js's export mode][export] (which is what this site does) or using [Astro][astro]. Both are great, choose your favorite. 125 | 126 | Let's dive in a second on how this course gets built. 127 | 128 | - [Here's the link to this page's markdown file][md] 129 | - [Here's the link to this site's Next.js config][config] 130 | - [Here's the link to this page's built HTML][html] 131 | - [Here's the link to the entire site's built-out HTML][site-html] 132 | 133 | Using this toolkit, I'm able to use React to write the wrappers of the pages of the course, write all the content in Markdown, and get all sorts of developer experience tools, SEO tools, and other tools that help make it easy for Frontend Masters to film these videos. I could write raw HTML or a Notion doc or something, but there's a ton of value being realized here by using Next.js 134 | 135 | [code]: https://github.com/btholt/intermediate-react-v6/ 136 | [export]: https://nextjs.org/docs/pages/building-your-application/deploying/static-exports 137 | [astro]: https://astro.build/ 138 | [md]: https://github.com/btholt/intermediate-react-v6/blob/main/lessons/02-react-render-modes/B-static-site-generation.md 139 | [config]: https://github.com/btholt/intermediate-react-v6/blob/main/next.config.js#L9 140 | [html]: https://github.com/btholt/intermediate-react-v6/blob/gh-pages/lessons/react-render-modes/static-site-generation.html 141 | [site-html]: https://github.com/btholt/intermediate-react-v6/tree/gh-pages 142 | -------------------------------------------------------------------------------- /lessons/02-react-render-modes/C-server-side-rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Discover how to enhance your React app's performance with server-side 4 | rendering (SSR) as demonstrated by Brian Holt in the Intermediate React v6 5 | course for Frontend Masters. Learn how to implement SSR by hand to optimize 6 | time to interactive and first meaningful paint, along with considerations for 7 | complexity and device performance. Explore practical examples of SSR app setup 8 | with Fastify, Vite, and React, including handling browser-only scripts and 9 | overcoming hydration errors. 10 | keywords: 11 | - React 12 | - server-side rendering 13 | - SSR 14 | - Brian Holt 15 | - Frontend Masters 16 | --- 17 | > 💡 The previous version of this course does a pretty in-depth dive on migrating a client-side React app to being server-side render. [Check it out here][v5]. Nothing has changed so if you want more SSR magic ✨ this still 100% applies. 18 | 19 | When you have a client-side React request, the general flow looks like this: 20 | 21 | ![User requests app, user waits for HTML and JS bundle to be requested, once downloaded they can finally see the app](/images/request1.png) 22 | 23 | Generally speaking, this is acceptable for many apps. A lot of apps you interact with on a daily basis work just fine like this. However, you may run into situations where it may behoove you to alter the performance profile of your app a bit. 24 | 25 | ![User requests app, server pre-renders app, users sees complete HTML while the React app bootstraps itself](/images/request2.png) 26 | 27 | Notice the time to interactive and time to first meaningful paint are different now. In the previous version they were the same moment, we have now separated them. 28 | 29 | You may be looking at this thinking "this is better!" and in many cases it is: people see something quickly and before they can decide to take an action, generally the app will be bootstrapped. It will _feel_ faster to the user despite the time to interactive will nearly always be tens of milliseconds later (as your app has to render on the server and that takes time). However be careful with this assumption as it in some cases it _isn't_ faster. 30 | 31 | - SSR carries with it complexity - some code can be executed in the browser and can't in Node.js (e.g. Google Analytics! it relies on browser APIs). You now need to cleanly separate what runs in browser and what doesn't. 32 | - On fast devices with fast connections, it will tend to actually be a bit slower to get both first paint and first interactive. If you're writing an app for iPhone 16 users in San Francisco, you really don't need SSR. If you're writing it for rural farmers in Montana or the Indian country-side, maybe SSR could help! 33 | - The key here is _measure_. SSR can be a great tool in your toolbox but make sure it's actually making a positive difference to your users. 34 | 35 | ## SSR by hand 36 | 37 | Okay, so let's write a very simple SSR app. 38 | 39 | In a new folder, run 40 | 41 | ```bash 42 | npx init -y 43 | npm i fastify @fastify/static react react-dom vite 44 | ``` 45 | 46 | Make sure your package.json has `"type": "module"` in it. Add this to the scripts: 47 | 48 | ```json 49 | "scripts": { 50 | "build": "vite build", 51 | "start": "node ./server.js" 52 | }, 53 | ``` 54 | 55 | In the project root, create an index.html 56 | 57 | ```html 58 | 59 | 60 | 61 | SSR Example 62 | 63 | 64 | 65 |
66 | 67 | 68 | ``` 69 | 70 | > 💡 We're doing vanilla JS again, but it's easier now to add JSX if you want to. Vite has `--ssr` flag you can add to compile an app for use w/ React doing SSR. We're not covering it today but feel free to try later. 71 | 72 | Create an App.js, put in there: 73 | 74 | ```javascript 75 | import { createElement as h, useState } from "react"; 76 | 77 | function App() { 78 | const [count, setCount] = useState(0); 79 | return h( 80 | "div", 81 | null, 82 | h("h1", null, "Hello Frontend Masters"), 83 | h("p", null, "This is SSR"), 84 | h("button", { onClick: () => setCount(count + 1) }, `Count: ${count}`) 85 | ); 86 | } 87 | 88 | export default App; 89 | ``` 90 | 91 | Now create a Client.js file 92 | 93 | ```javascript 94 | import { hydrateRoot } from "react-dom/client"; 95 | import { createElement as h } from "react"; 96 | import App from "./App.js"; 97 | 98 | hydrateRoot(document.getElementById("root"), h(App)); 99 | ``` 100 | 101 | This is code that will _only_ execute in the browser. If you have Google Analytics or local storage or anything like that, you'd do those sorts of things that _need_ to happen in the browser but don't need to be run in Node.js. Specifically, hydrateRoot will only run on the browser and can't run on the server. 102 | 103 | Now for the spicy bit, let's do server-side rendering 104 | 105 | ```javascript 106 | import fastify from "fastify"; 107 | import fastifyStatic from "@fastify/static"; 108 | import { readFileSync } from "node:fs"; 109 | import { fileURLToPath } from "node:url"; 110 | import path, { dirname } from "node:path"; 111 | import { renderToString } from "react-dom/server"; 112 | import { createElement as h } from "react"; 113 | import App from "./App.js"; 114 | 115 | const __filename = fileURLToPath(import.meta.url); 116 | const __dirname = dirname(__filename); 117 | 118 | const shell = readFileSync(path.join(__dirname, "dist", "index.html"), "utf8"); 119 | 120 | const app = fastify(); 121 | 122 | app.register(fastifyStatic, { 123 | root: path.join(__dirname, "dist"), 124 | prefix: "/", 125 | }); 126 | 127 | const parts = shell.split(""); 128 | app.get("/", (req, reply) => { 129 | reply.raw.write(parts[0]); 130 | const reactApp = renderToString(h(App)); 131 | reply.raw.write(reactApp); 132 | reply.raw.write(parts[1]); 133 | reply.raw.end(); 134 | }); 135 | 136 | app.listen({ 137 | port: 3000, 138 | }); 139 | ``` 140 | 141 | > If you're getting hydration errors, you may have a whitespace problem (I did when writing this.) React is _super_ sensitive to anything being different between client and server. I had to make sure that `
` had no newlines in it. 142 | 143 | The interesting part is the "/" get handler. We immediately write the head to the user. This allows the browser to see the script tag and immediately start downloading the React app. We then render the app and send it to the user. This means by the time the app is rendered and sent to the user, it'll be pretty close to the time the user finishes downloading the script and should get a faster time to first meaningful pain and a decent time to interactive. We finish it off by sending the rest of the closing tags to the user. 144 | 145 | > We did this with renderToString. You can do this with renderToPipeableStream as well and on a larger app see some improvement as it'll stream markup as it finishes to the user. I wanted to show you renderToString which works well for this micro app, but on a bigger codebase I'd choose streaming. We're about to do use renderToPipeableStream for React Server Components so you're about to learn how to use it anyway. 146 | 147 | [v5]: https://frontendmasters.com/courses/intermediate-react-v5/server-side-rendering/ 148 | -------------------------------------------------------------------------------- /lessons/02-react-render-modes/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "paint-roller" 3 | } 4 | -------------------------------------------------------------------------------- /lessons/03-rscs-without-a-framework/A-intro-to-react-server-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | In the Intermediate React v6 course by Brian Holt from Frontend Masters, 4 | explore the concept of React Server Components (RSCs) and their role in 5 | rendering components on the server for improved performance and security. 6 | Learn how RSCs differ from server-side rendering and how to implement them 7 | using frameworks like Next.js. The course also involves building a messaging 8 | app to practice hand-coding with RSCs. 9 | keywords: 10 | - React 11 | - Frontend Masters 12 | - Brian Holt 13 | - server components 14 | - Next.js 15 | - web development 16 | - RSC 17 | --- 18 | 19 | We've talked about client-side React apps (no server), static site generation apps (server runs only at compile time), and server-side rendered app (server renders initial page but then client does the rest). Now we're going to talk about react server components which involves the server even more. 20 | 21 | ## What are RSCs? 22 | 23 | RSCs are not SSR 2.0. 24 | 25 | This is a common misconception that I just want to dispel upfront: these things are relatively unrelated in the sense you can SSR without RSCs, RSCs without SSR, both, or neither! 26 | 27 | RSCs introduce the notion of a server to an ongoing React app. Whereas SSR renders the page once and then is no longer involved in the ongoing React app, RSCs introduce the notion that the server continues to render parts of the app. An RSC is a component that only renders on the server and sends its complete markup to the app – the client-side app doesn't even have the code to render the component client-side - it can only receive markup from a server and then insert that into the app. 28 | 29 | > A React server component is a component that _only_ renders on the server. 30 | 31 | Why is this useful? For a couple of reasons. No matter how much of an interactive app you have, large swaths of it are inert markup, text, and content. You don't need React to generate HTML that shows a few headers and paragraphs. We do it because it's simpler to keep all the code together, but the truth of it is just that: in the grand scheme of things, React is really only needed client-side to add interactivity to an app. Everything else is developer experience and code organization. 32 | 33 | Enter RSCs. For one, they're just a simple rendering of a page's content, rendered once, on the server. This means that it doesn't matter how powerful a user's device is, the rendering is happening on the server. It also means users aren't receiving the code for pages they aren't going to visit, so the bundle can be split pretty well. 34 | 35 | But the biggest, most powerful feature (in my opinion) is that RSCs can use modules that can only be used on the server. Let's say you're making a messaging app (like we're about to): that data all has to be stored in a database somewhere. Normally you'd need a React app fetch that data with a get request, but with RSCs you can just make the request directly in the React component 🤯 36 | 37 | All the secret connection strings you need to have to access your database? Never leave your server! It makes writing React components that read & write from databases, private APIs (like OpenAI, etc.), or need other server-side components very fluent to write. We're about to see how. 38 | 39 | ## Okay so don't write RSCs by hand 40 | 41 | But we're going to lol. 42 | 43 | RSCs are really meant to be written by frameworks and _not_ be written by the masses. It's a pretty deep integration to build with your bundler (and they only ship Webpack at the moment) and it's very hard to get right and not get a net-negative on performance. 44 | 45 | It's also just a lot of code to write, and there already a few frameworks out there that will do it better than you likely have time to. 46 | 47 | - [Next.js][next] 48 | - [React Router v7 / Remix][remix] (soon) 49 | - [TanStack Start][tanstack] (soon) 50 | 51 | Next.js works with RSCs today, as of the writing of this course, and embraces the full surface capabilities of RSCs. React Router v7 and TanStack Start both have expressed that at least initially they will be selective of which features that they embrace that make sense with their frameworks (and I support that! No reason to shoehorn it in.) 52 | 53 | We'll end up writing a whole app in Next.js by the end of the course, but we're going to start with doing it by hand! 54 | 55 | ## NotePasser 56 | 57 | Remember in school when you would pass notes by hand? No? Well, back in my day (_shakes fist at passing cloud_) we didn't have cell phones and had to rely on penmanship and stealthily trying to pass a paper note to our friend in class. 58 | 59 | We're going to build an app inspired by that today, where we can pass notes from one user to another. 60 | 61 | One note - I had originally built this app to have a full on auth provider, but realized it was adding a lot of complexity to the app that wasn't teaching you how to do React. Instead this app is going to sort of fake auth. We're just going to assume whoever is using the app is always user ID `1`. This is done in the name of simplicity so we can just focus on building React apps, but feel free to later to add a cool auth provider like [Neon Auth][neon] (I helped build this one!!), [Clerk][clerk], [Descope][descope], or any other of your favorites. 62 | 63 | [next]: https://nextjs.org/ 64 | [remix]: https://reactrouter.com/ 65 | [tanstack]: https://tanstack.com/start/latest 66 | [neon]: https://neon.tech/ 67 | [clerk]: https://clerk.com/ 68 | [descope]: https://www.descope.com/ 69 | -------------------------------------------------------------------------------- /lessons/03-rscs-without-a-framework/B-rsc-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RSC Dependencies 3 | description: >- 4 | Learn how to set up a React Server Components project from scratch using 5 | Webpack and Babel, with guidance from Brian Holt's Intermediate React v6 6 | course on Frontend Masters. This comprehensive guide includes configuring 7 | Webpack, Babel, and Fastify to run RSCs efficiently without additional 8 | frameworks, enhancing your web development skills. 9 | keywords: 10 | - React Server Components 11 | - Webpack 12 | - Babel 13 | - Brian Holt 14 | - Frontend Masters 15 | - RSC 16 | - web development 17 | --- 18 | 19 | There's a lot to getting this set up by hand, but bear with me, and I promise at the end of this you're going to understand RSCs at a depth that allows you to make trade-offs of when to use them and when to avoid them. 20 | 21 | So, first, we're going to use Webpack and Babel directly (despite normally I'd suggest Vite.) Why? Because this allows us to use the React team's code directly without a layer indirection between Vite and Webpack. In general I suggest Vite for React devs. 22 | 23 | So let's get our project started. In a new directory run 24 | 25 | ```bash 26 | npm init -y 27 | npm install @babel/core@7.26.8 @babel/plugin-transform-modules-commonjs@7.26.3 @babel/preset-react@7.26.3 @babel/register@7.25.9 @fastify/static@8.1.0 babel-loader@9.2.1 css-loader@7.1.2 doodle.css@0.0.2 fastify@5.2.1 html-webpack-plugin@5.6.3 nodemon@3.1.9 pino-pretty@13.0.0 promised-sqlite3@2.1.0 react@19.0.0 react-dom@19.0.0 react-server-dom-webpack@19.0.0 sqlite3@5.1.7 style-loader@4.0.0 webpack@5.97.1 webpack-cli@6.0.1 28 | ``` 29 | 30 | You can either run that, or just copy this package.json into your project and run `npm i`. 31 | 32 | ```json 33 | { 34 | "name": "no-framework", 35 | "version": "1.0.0", 36 | "main": "index.js", 37 | "scripts": {}, 38 | "keywords": [], 39 | "author": "Brian Holt", 40 | "license": "Apache-2.0", 41 | "description": "React Server Components without a framework!", 42 | "dependencies": { 43 | "@babel/core": "^7.26.8", 44 | "@babel/plugin-transform-modules-commonjs": "^7.26.3", 45 | "@babel/preset-react": "^7.26.3", 46 | "@babel/register": "^7.25.9", 47 | "@fastify/static": "^8.1.0", 48 | "babel-loader": "^9.2.1", 49 | "css-loader": "^7.1.2", 50 | "doodle.css": "^0.0.2", 51 | "fastify": "^5.2.1", 52 | "html-webpack-plugin": "^5.6.3", 53 | "nodemon": "^3.1.9", 54 | "pino-pretty": "^13.0.0", 55 | "promised-sqlite3": "^2.1.0", 56 | "react": "^19.0.0", 57 | "react-dom": "^19.0.0", 58 | "react-server-dom-webpack": "^19.0.0", 59 | "sqlite3": "^5.1.7", 60 | "style-loader": "^4.0.0", 61 | "webpack": "^5.97.1", 62 | "webpack-cli": "^6.0.1" 63 | } 64 | } 65 | ``` 66 | 67 | Either works! We need a lot of machinery to get this to work but the high level is we're going to be building a Fastify server that is going to be serving RSCs via the React Flight format. 68 | 69 | Let's set up Webpack. Create a webpack.config.js 70 | 71 | ```javascript 72 | const path = require("node:path"); 73 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 74 | const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin"); 75 | 76 | const mode = process.env.NODE_ENV || "development"; 77 | const development = mode === "development"; 78 | 79 | const config = { 80 | mode, 81 | entry: "./src/Client.jsx", 82 | module: { 83 | rules: [ 84 | { 85 | test: /\.jsx?$/, 86 | use: "babel-loader", 87 | exclude: /node_modules/, 88 | }, 89 | { 90 | test: /\.css$/i, 91 | use: ["style-loader", "css-loader"], 92 | }, 93 | ], 94 | }, 95 | resolve: { 96 | extensions: [".js", ".jsx"], 97 | }, 98 | plugins: [ 99 | new HtmlWebpackPlugin({ 100 | inject: true, 101 | publicPath: "/assets/", 102 | template: "./index.html", 103 | }), 104 | new ReactServerWebpackPlugin({ isServer: false }), 105 | ], 106 | output: { 107 | chunkFilename: development 108 | ? "[id].chunk.js" 109 | : "[id].[contenthash].chunk.js", 110 | path: path.resolve(__dirname, "dist"), 111 | filename: "[name].js", 112 | clean: true, 113 | }, 114 | optimization: { 115 | runtimeChunk: "single", 116 | }, 117 | }; 118 | 119 | module.exports = config; 120 | ``` 121 | 122 | This isn't a Webpack class so let's not dive too deep here - we're just making a Webpack config that's going to compile our React from JSX to usable JS code, use style-loader to load CSS, use the HTML plugin to generate a valid HTML for us, and make sure it's all living in one bundle so our React Flight protocol can find client side components in the bundle. 123 | 124 | Let's set up the Babel config. Make a babel.config.js file. 125 | 126 | ```javascript 127 | const development = (process.env.NODE_ENV || "development") === "development"; 128 | 129 | module.exports = { 130 | presets: [ 131 | [ 132 | "@babel/preset-react", 133 | { 134 | runtime: "automatic", 135 | useSpread: true, 136 | development: true, 137 | }, 138 | ], 139 | ], 140 | }; 141 | ``` 142 | 143 | Now everything will work with Babel. 144 | 145 | Let's make an index.html 146 | 147 | ```html 148 | 149 | 150 | 151 | 152 | 153 | React Server Components without a Framework! 154 | 155 | 156 | 157 |
158 | 159 | 160 | ``` 161 | 162 | Looks quite similar to our previous ones. Let's make a /public/index.css. [Copy it from here][css] 163 | 164 | Copy [this SQLite file][sqlite] to your root directory as well. 165 | 166 | > Both of these files should be in the root directory of the project if you cloned it. notes.db and index.css. Put them into your project 167 | 168 | Lastly add these scripts to your package.json 169 | 170 | ```json 171 | "scripts": { 172 | "dev:client": "webpack --watch", 173 | "dev:server": "node --watch --conditions react-server server/main.js" 174 | }, 175 | ``` 176 | 177 | Okay, this should give us everything that's needed for our app to function before we actually write the server and the React app. One bit to highlight is `const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");` in our Webpack file - this is the magic plugin that will allow Webpack to _not_ render server components and only include client components. Other than that, this is a very standard React + Webpack set up. 178 | 179 | Another new thing you might be the `--conditions react-server` part of running our server app. This lets Node.js know how to resolve its modules - we're in a react-server condition so only import those and know not to import client modules. I had never used this feature of Node.js before but it's pretty cool, even if it's a bit niche. 180 | 181 | [css]: https://raw.githubusercontent.com/btholt/irv6-project/refs/heads/main/completed/no-framework/public/index.css 182 | [sqlite]: https://github.com/btholt/irv6-project/blob/main/notes.db 183 | -------------------------------------------------------------------------------- /lessons/03-rscs-without-a-framework/C-server-and-client-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to build a simple React app with server and client components using 4 | React Server Components as taught by Brian Holt in Intermediate React v6 for 5 | Frontend Masters. Discover the differences between client and server 6 | components, and how to implement async functions in server components for 7 | efficient rendering. 8 | keywords: 9 | - React 10 | - Server Components 11 | - Brian Holt 12 | - Frontend Masters 13 | - JavaScript 14 | --- 15 | 16 | Let's put together the most simple React app so that we can render it using our newly-created framework. 17 | 18 | Let's make a directory, `src` and put a file in there a file called App.jsx 19 | 20 | ```javascript 21 | import { Suspense } from "react"; 22 | import ServerComponent from "./ServerComponent"; 23 | import ClientComponent from "./ClientComponent"; 24 | 25 | export default function App() { 26 | console.log("rendering App server component"); 27 | return ( 28 | Loading...}> 29 |

Notes App

30 | 31 | 32 |
33 | ); 34 | } 35 | ``` 36 | 37 | We're going to putting a lot of console logs in here just so you can see where things are happening and in what order. Great, so we'll have two components: a client and a server component to show you the difference. The client component won't render at all on the server and will be included in the bundle. Likewise the server component will only be rendered in the server and won't be included in the bundle. 38 | 39 | So where does the App component render? On the server. By default _everything_ renders on the server. By default you can't use any hooks that have interactivity like useState; you have to declare it a client component to do that. 40 | 41 | Okay, so let's make our client component. Make ClientComponent.jsx and put in there 42 | 43 | ```javascript 44 | "use client"; 45 | 46 | import { useState } from "react"; 47 | 48 | export default function ClientComponent() { 49 | console.log("rendering ClientComponent client component"); 50 | const [counter, setCounter] = useState(0); 51 | 52 | return ( 53 |
54 | Client Component 55 |

Counter: {counter}

56 | 57 |
58 | ); 59 | } 60 | ``` 61 | 62 | Nothing you haven't seen before except the weird `"use client";` at the top. This is how you declare a component is to be rendered on the client and not on the server. After that it's just React as you know and love it. One note: `"use server;"` is not necessary, it's assumed. 63 | 64 | Okay, let's make `ServerComponent.jsx` 65 | 66 | ```javascript 67 | import { AsyncDatabase } from "promised-sqlite3"; 68 | import path from "node:path"; 69 | 70 | // this page assumes that you are logged in as user 1 71 | export default async function MyNotes() { 72 | console.log("rendering MyNotes server component"); 73 | async function fetchNotes() { 74 | console.log("running server function fetchNotes"); 75 | const dbPath = path.resolve(__dirname, "../../notes.db"); 76 | const db = await AsyncDatabase.open(dbPath); 77 | const from = await db.all( 78 | "SELECT n.id as id, n.note as note, f.name as from_user, t.name as to_user FROM notes n JOIN users f ON f.id = n.from_user JOIN users t ON t.id = n.to_user WHERE from_user = ?", 79 | ["1"] 80 | ); 81 | return { 82 | from, 83 | }; 84 | } 85 | 86 | const notes = await fetchNotes(); 87 | 88 | return ( 89 |
90 | Server Component 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | {notes.from.map(({ id, note, from_user, to_user }) => ( 102 | 103 | 104 | 105 | 106 | 107 | ))} 108 | 109 |
FromToNote
{from_user}{to_user}{note}
110 |
111 |
112 | ); 113 | } 114 | ``` 115 | 116 | - We're doing SQL in React!? Well, yes, but also no. This all happening server-side before it's being sent down to the browser. So it's basically the same as doing an API request but it's doing the full React lifecycle here on the server. That's one of the nicest aspects of RSC - you get to write all of this as if it was being done server side but instead of having a server portion and a client portion, it's all the same file! 117 | - Notice that it's an `async` function - this is a fun ability that only server components have. Since it's all rendering once and on the server, you can do async functions for react server components. 118 | 119 | This is deliberately a pared-down feature set as we're going to only do the bare minimum to implement a by-hand RSC-server implementation. Once we get into Next.js I'll show you more advance React server component features. 120 | 121 | Okay, one more file, call it Client.jsx and put this in there. 122 | 123 | ```javascript 124 | import { createRoot } from "react-dom/client"; 125 | import { createFromFetch } from "react-server-dom-webpack/client"; 126 | import "doodle.css/doodle.css"; 127 | 128 | console.log("fetching flight response"); 129 | const fetchPromise = fetch("/react-flight"); 130 | const root = createRoot(document.getElementById("root")); 131 | const p = createFromFetch(fetchPromise); 132 | console.log("rendering root", p); 133 | root.render(p); 134 | ``` 135 | 136 | `createFromFetch` allows us to turn a fetch request to an API endpoint into a React component directly. This is the magic of React server components and probably one you'll never write by hand again - your framework will always do this for you. But I wanted to demystify what it's doing for you. You make a request to an API endpoint, get a promise, and hand it to React to render. That's it! 137 | -------------------------------------------------------------------------------- /lessons/03-rscs-without-a-framework/D-the-rsc-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The RSC Server 3 | description: >- 4 | Explore how to use Fastify to serve a React app with server components by 5 | setting up a Node.js server that handles both client-side and server-side 6 | rendering. Written by Brian Holt for Frontend Masters, this guide offers 7 | step-by-step integration of React and Fastify, demonstrating the use of React 8 | Flight Protocol and reinforcing React's modular framework for advanced web 9 | development. 10 | keywords: 11 | - React 12 | - Fastify 13 | - Node.js 14 | - Brian Holt 15 | - server components 16 | - web development 17 | - React Flight Protocol 18 | --- 19 | 20 | So now we have a React app ready to be served to clients. Let's write a quick Fastify server to do this. I just chose Fastify because I like it; there's no reason you couldn't do Express, Restify, or any other API framework for Node.js. 21 | 22 | Let's create a `server` directory in the root directory of our project. Put in there a main.js file and put in there 23 | 24 | ```javascript 25 | const reactServerregister = require("react-server-dom-webpack/node-register"); 26 | reactServerregister(); 27 | 28 | const babelRegister = require("@babel/register"); 29 | babelRegister({ 30 | ignore: [/[\\\/](dist|server|node_modules)[\\\/]/], 31 | plugins: ["@babel/transform-modules-commonjs"], 32 | }); 33 | 34 | require("./server")(); 35 | ``` 36 | 37 | - We're doing CommonJS for our server portion. I had it working with ES modules but it took so much extra code that this was simpler to teach you. Feel free to convert it later yourself. Everything else in this course will be ES modules. 38 | - This file does three things: it makes our Node.js server able to read React/JSX files, it makes it able to ES modules in our client code, and it makes it able to do RSCs via the webpack bundler by hooking into the module systems to render some portions and not others. Remember the `--conditions react-server` that we have in our CLI command? This is what that is for. 39 | - Why is this in a separate file? It's so all subsequent `require`/`import` calls can be run through Babel and the react-server-dom-webpack modules. 40 | 41 | Okay, let's start crafting our server.js file in the same directory. Let's just start with the basic machinery. 42 | 43 | ```javascript 44 | const path = require("node:path"); 45 | const { readFileSync } = require("node:fs"); 46 | const Fastify = require("fastify"); 47 | const fastifyStaticPlugin = require("@fastify/static"); 48 | const React = require("react"); 49 | const { renderToPipeableStream } = require("react-server-dom-webpack/server"); 50 | const AppImport = require("../src/App.jsx"); 51 | 52 | const App = AppImport.default; 53 | 54 | const MANIFEST = readFileSync( 55 | path.resolve(__dirname, "../dist/react-client-manifest.json"), 56 | "utf8" 57 | ); 58 | const MODULE_MAP = JSON.parse(MANIFEST); 59 | const PORT = process.env.PORT ? process.env.PORT : 3000; 60 | 61 | const fastify = Fastify({ 62 | logger: { 63 | transport: { 64 | target: "pino-pretty", 65 | }, 66 | }, 67 | }); 68 | 69 | fastify.register(fastifyStaticPlugin, { 70 | root: path.join(__dirname, "../dist"), 71 | prefix: "/assets/", 72 | }); 73 | 74 | fastify.register(fastifyStaticPlugin, { 75 | root: path.join(__dirname, "../public"), 76 | decorateReply: false, 77 | }); 78 | 79 | fastify.get("/", async function rootHandler(request, reply) { 80 | return reply.sendFile("index.html"); 81 | }); 82 | 83 | fastify.get("/react-flight", function reactFlightHandler(request, reply) { 84 | // we'll do this code in a sec 85 | }); 86 | 87 | module.exports = async function start() { 88 | try { 89 | await fastify.listen({ port: PORT }); 90 | } catch (err) { 91 | fastify.log.error(err); 92 | process.exit(1); 93 | } 94 | }; 95 | ``` 96 | 97 | - Pretty standard Fastify app. We're serving two static folders, `public` which is our CSS but also could be images and some other stuff. It's stuff that doesn't need to be compiled and just served directly. `dist` is our compiled Webpack stuff. 98 | - We're importing `react-server-dom-webpack/server` to be able to correctly serve our app here momentarily. 99 | - Same with our App.jsx. Notice we're not importing Client.jsx - that's all code that'll only execute client side. 100 | - The module map is something that `react-server-dom-webpack` generates and let's React how to know what module to what. 101 | 102 | Alright, let's render our app next. But let's cheat. Let's just give it directly some "React Flight" to render instead of the actual app. 103 | 104 | ```javascript 105 | // replace the react-flight route 106 | fastify.get("/react-flight", function reactFlightHandler(request, reply) { 107 | try { 108 | reply.header("Content-Type", "application/octet-stream"); 109 | // be careful about whitespace as React Flight is sensitive to it. Make your editor isn't inserting any 110 | return reply.send(`1:{"name":"App","env":"Server","key":null,"owner":null,"props":{}} 111 | 0:D"$1" 112 | 0:["$","div",null,{"children":["$","h1",null,{"children":"Notes App"},"$1"]},"$1"] 113 | `); 114 | } catch (err) { 115 | request.log.error("react-flight err", err); 116 | throw err; 117 | } 118 | }); 119 | ``` 120 | 121 | > 💡 This is prone to breaking as this protocol is unstable and they may choose to change it in the future. However the principles remain the same. If you see something about webpack complaining in the errors, just skip this code sample and go to the next code block. 122 | 123 | - So what is that weird string in there? It's called React Flight Protocol and it's how React's server and client protocol work with each other. 124 | - You'll never write this by hand. But I wanted to show you it's not very scary, it's just a DSL for communicating between a client and a server, and in theory you could hand author it - you just never would. 125 | 126 | Let's take it one half step further. 127 | 128 | ```javascript 129 | // replace reply.send 130 | 131 | // be careful about whitespace as React Flight is sensitive to it. Make your editor isn't inserting any 132 | return reply.send(`3:I["./src/ClientComponent.jsx",["vendors-node_modules_react_jsx-dev-runtime_js","vendors-node_modules_react_jsx-dev-runtime_js.chunk.js","client0","client0.chunk.js"],""] 133 | 1:{"name":"App","env":"Server","key":null,"owner":null,"props":{}} 134 | 0:D"$1" 135 | 0:["$","div",null,{"children":[["$","h1",null,{"children":"Notes App"},"$1"],["$","$L3",null,{},"$1"]]},"$1"] 136 | `); 137 | ``` 138 | 139 | - This may not work for you due to paths (and again, you'd never hand author this so don't worry if it doesn't.) 140 | - This is cool though because you can see how React is referencing a client component. It's basically saying here's a div, and in it has an h1 and a "$L3". It then defines a "3" on the first line where it has `3:I`. Here it's saying anywhere that you have a 3 component, it refers to a ClientComponent, and that's going to be in this specific bundle, go find that and use it. 141 | - Pretty cool, right? 142 | - I genuinely don't know what some of the pieces here do, but it's fun to hack around and see what you can bend React to do. 143 | 144 | Okay, let's actually put the real code in here. 145 | 146 | ```javascript 147 | fastify.get("/react-flight", function reactFlightHandler(request, reply) { 148 | try { 149 | reply.header("Content-Type", "application/octet-stream"); 150 | const { pipe } = renderToPipeableStream( 151 | React.createElement(App), 152 | MODULE_MAP 153 | ); 154 | pipe(reply.raw); 155 | } catch (err) { 156 | request.log.error("react-flight err", err); 157 | throw err; 158 | } 159 | }); 160 | ``` 161 | 162 | - This will actually render your app based on what your App.jsx has (whereas we were "rendering" it ourselves before.) 163 | - The way we coded this it will only really work for the base route of your app. If you tried to introduce a router or more components here, we haven't provided for the ability to route to different routes. You'd have to code that up yourself. 164 | - Likewise we're not handing any props to App. 165 | - This is _not_ server-side rendering. The way you'd do that is you'd render the React Flight protocol, dump it into a script tag in the HTML, and have your app read that and use it to render your app on load. A little awkward but again, generally your framework will handle that for you. 166 | - Check out the react-flight request in your network tab to see how it gets rendered out. 167 | - I also like it shows you `Server` logs directly in the browser, makes it easy to see client and server console.logs in the same place. 168 | 169 | So there you go! Now your app is rendering partially on the server, partially on the client, and you've hand-coded it all! This is a pain to do but once you involve a framework like Next.js this gets so much easier. Still, amazing job! Hopefully you peeked a little bit behind the curtain to see what React is doing for you. 170 | -------------------------------------------------------------------------------- /lessons/03-rscs-without-a-framework/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "RSCs without a Framework", 3 | "icon": "file-code" 4 | } 5 | -------------------------------------------------------------------------------- /lessons/04-rscs-with-nextjs/A-abbreviated-intro-to-nextjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Abbreviated Intro to Next.js 3 | description: >- 4 | Explore Next.js and React Server Components (RSC) in the "Intermediate React 5 | v6" course by Brian Holt for Frontend Masters. This guide uses Next.js as a 6 | tool to demonstrate RSCs, covering basic setup, dependencies, and the 7 | implementation of essential page components for developing apps like Note 8 | Passer. Begin your Next.js journey with practical insights on integrating 9 | versatile front-end frameworks with a wide range of back-end solutions for web 10 | development success. 11 | keywords: 12 | - Next.js 13 | - React Server Components 14 | - Brian Holt 15 | - Frontend Masters 16 | - web development 17 | --- 18 | 19 | This is not an intro to Next.js course, intentionally. Frontend Masters has an amazing teacher for that, Scott Moss. We are going to use Next.js here, but only as a tool to teach about RSCs. We'll leave the rest of "how to write great Next.js code" to Scott and his Intro to Next.js Course. You'll learn some Next.js incidentally, but we'll be glossing over it. 20 | 21 | Next.js is a fantastic React.js complete framework built by the fine folks at Vercel. Much of the React core team works at Vercel and therefore a lot of the newest changes show up in Next.js first because it's the same people working on both. 22 | 23 | Next.js has gone through several iterations but the latest version makes RSCs a first class citizen where everything is an RSC by default and you have to opt into client-side components. I'll say this has some trade-offs - RSCs are more complicated to deal with, but it's definitely a good opinion for lots of apps out there. It's worth having as a tool in your tool belt. 24 | 25 | You can use Next.js in one of two ways (in my opinion): it can be your entirely monolithic server (great answer for some apps) or you can have it as a "middle-end" server where it just serves your front-end and you have another server that's just your backend (this could be a Fastify, FastAPI, Flask, Sinatra, etc.) server. 26 | 27 | Let's get our app started 28 | 29 | ```javascript 30 | npx create-next-app@15.1.7 --js --app --src-dir --turbopack 31 | ``` 32 | 33 | You can call the app whatever you want. I'd omit ESLint and Tailwind (no big deal if you want to include them, we're just not going to use them today.) 34 | 35 | Okay, let's shape this app to be what we need. We're going to continue on our path to building Note Passer, so let's install a few more dependencies: 36 | 37 | ```javascript 38 | npm i doodle.css@0.0.2 promised-sqlite3@2.1.0 sqlite3@5.1.7 39 | ``` 40 | 41 | Now let's create our DB. Here's a few options for you 42 | 43 | - Download [this notes.db file][db] and put it in the root directory of your project 44 | - Download [this seed SQL file][seed] and use it to set up your SQLite DB 45 | 46 | Either works. You can copy/paste that seed file into SQLite session or you can also run `.read seed.sql` (if seed is in the same directory as the notes.db file) to read the local file. 47 | 48 | Next we'll modify src/page.js 49 | 50 | ```javascript 51 | import Link from "next/link"; 52 | 53 | export default function Home() { 54 | return ( 55 |
56 |
    57 |
  • 58 | My Notes 59 |
  • 60 |
  • 61 | Write a Note 62 |
  • 63 |
  • 64 | Secret Teacher Feed 65 |
  • 66 |
  • 67 | Who Am I 68 |
  • 69 |
70 |
71 | ); 72 | } 73 | ``` 74 | 75 | And now layout.js 76 | 77 | ```javascript 78 | import Link from "next/link"; 79 | import "doodle.css/doodle.css"; 80 | import "./globals.css"; 81 | 82 | export const metadata = { 83 | title: "Note Passer", 84 | description: "Example app for Frontend Masters", 85 | }; 86 | 87 | export default async function RootLayout({ children }) { 88 | return ( 89 | 90 | 91 | 96 | {children} 97 | 98 | 99 | ); 100 | } 101 | ``` 102 | 103 | Great! This should be all fairly non-interesting. The only thing here is that page.js will be `/` homepage for our app and layout.js will be the layout file that wraps all inner components. This is a Next.js thing with their App router. Again, no need to worry about any of that right now - you'll cover App router more in-depth in the Intro to Next.js class. 104 | 105 | Lastly, update [the globals.css to be this][css]. Go ahead and delete page.module.css. CSS modules are awesome but not in scope for this class so I've just written all the CSS for you. 106 | 107 | [seed]: 108 | [db]: 109 | [css]: 110 | -------------------------------------------------------------------------------- /lessons/04-rscs-with-nextjs/B-server-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to create a server component in React to display user-specific notes 4 | using SQLite, as part of the Intermediate React v6 course taught by Brian Holt 5 | for Frontend Masters. This lesson guides you through setting up a route to 6 | fetch and display notes for a logged-in user, leveraging async functions and 7 | server components. 8 | keywords: 9 | - React 10 | - server components 11 | - SQLite 12 | - Brian Holt 13 | - Frontend Masters 14 | - web development 15 | - async functions 16 | --- 17 | 18 | So let's now do a server component where a user can read their own individual notes. This will look a lot like what we did with the no framework version! 19 | 20 | Make a folder inside the app directory called `my`. Inside that directory, put page.js. This will make the route /my where this page will show up. 21 | 22 | ```javascript 23 | import { AsyncDatabase } from "promised-sqlite3"; 24 | 25 | // this page assumes that you are logged in as user 1 26 | export default async function MyNotes() { 27 | async function fetchNotes() { 28 | const db = await AsyncDatabase.open("./notes.db"); 29 | const fromPromise = db.all( 30 | "SELECT n.id as id, n.note as note, f.name as from_user, t.name as to_user FROM notes n JOIN users f ON f.id = n.from_user JOIN users t ON t.id = n.to_user WHERE from_user = ?", 31 | ["1"] 32 | ); 33 | const toPromise = db.all( 34 | "SELECT n.id as id, n.note as note, f.name as from_user, t.name as to_user FROM notes n JOIN users f ON f.id = n.from_user JOIN users t ON t.id = n.to_user WHERE to_user = ?", 35 | ["1"] 36 | ); 37 | const [from, to] = await Promise.all([fromPromise, toPromise]); 38 | return { 39 | from, 40 | to, 41 | }; 42 | } 43 | 44 | const notes = await fetchNotes(); 45 | 46 | return ( 47 |
48 |

My Notes

49 |
50 | Notes To You 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {notes.to.map(({ id, note, from_user, to_user }) => ( 61 | 62 | 63 | 64 | 65 | 66 | ))} 67 | 68 |
FromToNote
{from_user}{to_user}{note}
69 |
70 |
71 | Notes From You 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {notes.from.map(({ id, note, from_user, to_user }) => ( 82 | 83 | 84 | 85 | 86 | 87 | ))} 88 | 89 |
FromToNote
{from_user}{to_user}{note}
90 |
91 |
92 | ); 93 | } 94 | ``` 95 | 96 | - We've built this to essentially the user is always logged in as user 1, brian. Feel free afterwards to add your own auth and make it a full-fledged app. [Neon Auth][neon] (which I helped build!) and [Clerk][clerk] are two great options here. 97 | - It's an async function 98 | - We're able to use the SQLite driver which is server-only normally 99 | - Again, we don't have to say `"use server";` because it's assumed that any component without "use client" is a server component 100 | - Make sure to check out the network traffic! It's cool to see all the React Flight protocol stuff in action! 101 | 102 | [neon]: https://neon.tech/blog/neon-auth-is-here-get-authentication-in-a-couple-of-clicks 103 | [clerk]: https://clerk.com 104 | -------------------------------------------------------------------------------- /lessons/04-rscs-with-nextjs/C-server-actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to create server actions in React using a backend function 4 | integration with forms, simplifying the process of managing form data in this 5 | lesson from Brian Holt's Intermediate React v6 course from Frontend Masters. 6 | This tutorial covers the implementation of server components to automatically 7 | handle data transport from frontend to backend without manual 'fetch' coding. 8 | keywords: 9 | - React 10 | - server actions 11 | - frontend 12 | - backend 13 | - form integration 14 | - Brian Holt 15 | - Frontend Masters 16 | --- 17 | One of the most common interactions that users have with websites is some variation on submitting a form - whether that's a very traditional "fill out this form and hit submit" or something similar where the developer uses a form under the hood to handle inputs and then uses a submit event to fire off an API call. 18 | 19 | The React team leaned into this with a feature called server actions. You can essentially create an RSC that has an action on the form that will cause an action to fire on the server. It makes it really simple to hook up a frontend form and a backend function to process it. Let's first craft our form. 20 | 21 | Make a folder inside app called `write`. In there put page.js, so that we can have the route `/write`. 22 | 23 | ```javascript 24 | import { AsyncDatabase } from "promised-sqlite3"; 25 | import postNote from "./postNote"; 26 | 27 | export default async function Write() { 28 | async function getUsers() { 29 | const db = await AsyncDatabase.open("./notes.db"); 30 | return db.all("SELECT * FROM users"); 31 | } 32 | const users = await getUsers(); 33 | 34 | return ( 35 |
36 |
37 | Write a new note 38 |
39 | 49 | 59 |
102 | 103 | ); 104 | } 105 | ``` 106 | 107 | Alright, go play with it now (you may need to mess with the JANK_DELAY as well as the interval of how often the interval runs). The scroll is probably either janky or it has a hard time re-rendering. Typing in it should hard as well. Also notice that the current render. Re-rendering the theme is tough too. 108 | 109 | So how can we fix at least the scroll portion, as well make the other two a little less painful (as they'll only re-rendering once as opposed to continually.) 110 | 111 | [xss]: https://owasp.org/www-community/attacks/xss/ 112 | [starter]: https://github.com/btholt/irv6-project/tree/main/starter/perf 113 | [completed]: https://github.com/btholt/irv6-project/tree/main/completed/perf 114 | -------------------------------------------------------------------------------- /lessons/05-performance-optimizations/C-fixing-it.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | This guide explains how to optimize React component re-rendering using 4 | memoization techniques such as the memo function, useCallback, and useMemo, 5 | highlighting how these can prevent unnecessary re-renders by ensuring that a 6 | component only re-renders when its props or state variables change. Authored 7 | by Brian Holt for the Intermediate React v6 course, offered by Frontend 8 | Masters, this lesson is essential for web developers seeking to enhance React 9 | performance and manage state efficiently in their applications. 10 | keywords: 11 | - React 12 | - memoization 13 | - useCallback 14 | - useMemo 15 | - Brian Holt 16 | - Frontend Masters 17 | - performance 18 | --- 19 | 20 | When does a component re-render? It re-renders when its parents have changed, always. But what if we could say "only re-render when your props have changed"? Frankly that's most components, but this is an opt-in pattern for React. Generally, like we said, renders are so cheap that introducing this "memoization" layer just makes it harder to debug. But in this case we need some help, so let's do it! 21 | 22 | In MarkdownPreview.jsx 23 | 24 | ```javascript 25 | // at top 26 | import { memo } from "react"; 27 | 28 | // wrap function 29 | export default memo(function MarkdownPreview({ render, options }) { 30 | // code 31 | }); 32 | ``` 33 | 34 | Now we've told React "only re-render this when the props haven't changed. But it's still re-rendering? Why? 35 | 36 | Well, answer me two questions 37 | 38 | ```javascript 39 | const objectA = {}; 40 | const objectB = {}; 41 | 42 | console.log(objectA === objectB); // is this true or false? 43 | 44 | const functionA = function () {}; 45 | const functionB = function () {}; 46 | 47 | console.log(functionA === functionB); // is this true or false? 48 | ``` 49 | 50 | If you run this, what will be the two logs? Go ahead and try it. Copy and paste it into your console. 51 | 52 | Did you get two `false`? It's because despite being equivalent in terms of what we've instantiated them with, they are two separate entities. They're two different pointers, if you're familiar with C++ or other languages. 53 | 54 | So with our example, despite our functions and objects being constructed equivalently like in our little example here, they're considered **not equal** when React compares them. 55 | 56 | Well, damn, okay, how do we deal with that? useCallback and useMemo. Let's go do it in our app. 57 | 58 | ```javascript 59 | // at top 60 | import { useState, useCallback, useMemo } from "react"; 61 | 62 | // replace our options and render 63 | const render = useCallback((text) => marked.parse(text), []); 64 | const options = useMemo(() => ({ text, theme }), [text, theme]); 65 | ``` 66 | 67 | - There you go! Now it should be rendering and only re-rendering when needed! 🎉 68 | - Now it gives _the same_ options and _the same_ render each render so the `===` comparison will be true. 69 | - The array following it works just like useEffect - it's essentially the cache key. If one of those things changes in the array, it invalidates the memo/callback and makes it render again. 70 | -------------------------------------------------------------------------------- /lessons/05-performance-optimizations/D-other-perf-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Explore the differences between useMemo and useCallback Hooks in React, as 4 | explained by Brian Holt in the Intermediate React v6 course for Frontend 5 | Masters. Understand when to use these Hooks for performance optimization and 6 | learn about the potential pitfalls of overusing memoization, alongside 7 | insights into React Compiler. Enhance your React skills with practical advice 8 | on state management and performance enhancement techniques. 9 | keywords: 10 | - useMemo 11 | - useCallback 12 | - React Hooks 13 | - performance optimization 14 | - memoization 15 | - React Compiler 16 | - Brian Holt 17 | --- 18 | 19 | ## useMemo vs useCallback 20 | 21 | What's the difference between useCallback and useMemo? Not much. You could actually rewrite our code to look like this and it'd be equivalent. 22 | 23 | ```javascript 24 | // render and render2 are functionally equivalent 25 | const render = useMemo(() => (text) => marked.parse(text), []); 26 | const render2 = useCallback((text) => marked.parse(text), []); 27 | ``` 28 | 29 | Notice there's an extra `() => [fn]`. useCallback is only a thin wrapper on useMemo that automatically wraps it in the additional function. It's otherwise 100% equivalent. 30 | 31 | ## What about strings and numbers? 32 | 33 | If you're just passing in strings and numbers to a `memo`ized function, you don't need useMemo or useCallback since those will just pass the `===` test already. It's just objects and functions! So instead of having options as an object, we could have had theme and text as top level props and we could have skipped using useMemo! 34 | 35 | That's it! While you should use these tools sparingly, they're handy when you do need them. They let you create a better performance profile when it's important. 36 | 37 | ## Don't just use memo all the time 🙏 38 | 39 | As your potential future coworker, please please please don't just use memo on every function. I know it sounds like a good idea but let me tell you why. When you memoize something, it doesn't re-render when its props haven't changed. You're correct in thinking that is normally true of most components. However I **guarantee** you will start getting bugs of a component not re-rendering when you expect it to, and that is a hard bug to find if it's an errant memo somewhere in your codebase (spoken by someone who has had to debug this.) Just don't do it. 40 | 41 | ## React Compiler 42 | 43 | We talk a bit about [React Compiler][compiler] in the intro course, but let's mention it again here. The function of React Compiler is to examine your code and determine what components could be memoized without causing unexpected behavior. So just what I told you not to do? React is actually going to do this for you free. It's still in a rough alpha but they're encouraging people to try it out. 44 | 45 | [See my Intro lesson on it][intro]. 46 | 47 | [compiler]: https://react.dev/learn/react-compiler 48 | [intro]: https://react-v9.holt.courses/lessons/whats-next/react-compiler 49 | -------------------------------------------------------------------------------- /lessons/05-performance-optimizations/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "gauge-high" 3 | } 4 | -------------------------------------------------------------------------------- /lessons/06-transitions/A-what-are-transitions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to use React's useTransition hook to optimize user experience when 4 | selecting options with poor network connectivity, ensuring the user interface 5 | remains interactive by building a sports score app with delayed API responses, 6 | as taught by Brian Holt in the 'Intermediate React v6' course for Frontend 7 | Masters. 8 | keywords: 9 | - React 10 | - useTransition 11 | - Brian Holt 12 | - Frontend Masters 13 | - UI optimization 14 | - web development 15 | - sports app 16 | --- 17 | 18 | You know how your remote has those buttons for streaming services on it? Have you ever accidentally pressed "Uncle Pete's Streaming Service" instead of "Netflix" and it takes forever to load and you're held hostage to the unoptimized loading of their crappy app? Really frustrating, right? 19 | 20 | Now think of that happening on a website you've been on – you have several options to pick from and you accidentally choose the wrong one, and now you have to wait for the UI to be interactive again for you to be able to correct your mistake. Really annoying, especially on slower sites, slower connections and/or slower devices. 21 | 22 | This is what transitions are for in React. They allow you to keep UIs interactive while loading is happening behind the scene. 23 | 24 | We're going to build a little app that shows sports scores with an intentionally slow API. We're going to make it so users who accidentally select the wrong score to look at can still select a new game even while others are loading in the background. 25 | 26 | Most React apps in the past would have just locked the user out of being able to make new choices while data is loading - it's the easy way to code this up. But with the `useTransition` hook, this became pretty easy to do. Let's do it. 27 | -------------------------------------------------------------------------------- /lessons/06-transitions/B-the-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Explore Brian Holt's 'Intermediate React v6' course where you'll build a 4 | scoreboard project using Vite and Node.js, focusing on transitions without 5 | server-side dependencies. Discover how to set up and run the project with 6 | practical guides, making use of the provided starter and completed templates 7 | from GitHub. 8 | keywords: 9 | - React 10 | - Vite 11 | - Brian Holt 12 | - Node.js 13 | - frontend 14 | - project setup 15 | - Github templates 16 | --- 17 | > You will need to open the folder `transitions` from the repo in your project. 18 | > 19 | > - [The starter template][starter] 20 | > - [The completed project][completed] 21 | 22 | We're making a little scoreboard project, let's familiar ourselves with it. 23 | 24 | - Run `npm install` 25 | - It's a plain ol' Vite project. No server side anything. You can do transitions with servers, we just don't need to. 26 | - There's plain little Node.js server that just allow you to request the score of games 1 through 5. It doesn't do anything else. 27 | - Run `npm run dev:server` in one terminal and `npm run dev:client` in another. One runs Vite and one runs the Node.js server. 28 | - Vite will proxy the Node.js server from port 3000 to port 5173 so you can call localhost:5173/score?game=1 - all same origin 29 | - Everything else is a pretty standard React project. 30 | 31 | [starter]: https://github.com/btholt/irv6-project/tree/main/starter/transitions 32 | [completed]: https://github.com/btholt/irv6-project/tree/main/completed/transitions 33 | -------------------------------------------------------------------------------- /lessons/06-transitions/C-making-it-work-without-transitions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn about handling UI transitions and pending states in React applications 4 | with this practical example from Brian Holt's "Intermediate React v6." This 5 | lesson provides code snippets for managing API requests and displaying scores 6 | using a "Score" component, focusing on user experience improvements during 7 | data fetching. 8 | keywords: 9 | - React 10 | - UI transitions 11 | - pending states 12 | - Brian Holt 13 | - FrontEnd Masters 14 | - JavaScript 15 | - Intermediate React 16 | --- 17 | 18 | Let's first make this "wrong" first - the problem where the UI locks up during transitional times. 19 | 20 | Let's make a `Score.jsx` file 21 | 22 | ```javascript 23 | const loadingUrl = "/images/loading.webp"; 24 | export default function Score({ 25 | isPending, 26 | home, 27 | away, 28 | awayName, 29 | homeName, 30 | awayImage, 31 | homeImage, 32 | }) { 33 | return ( 34 |
35 |
36 |

{isPending ? "HOME" : homeName}

37 |

{isPending ? "–" : home}

38 | home team logo 39 |
40 |
41 |

{isPending ? "AWAY" : awayName}

42 |

{isPending ? "–" : away}

43 | away team logo 44 |
45 |
46 | ); 47 | } 48 | ``` 49 | 50 | - Key here is that if the score is pending, we show a dash. That will visually indicate to the user we haven't loaded it yet. 51 | 52 | Okay, let's make a quick function fetch from the API. Make a file called `getScore.js` 53 | 54 | ```javascript 55 | export default async function getScore(game) { 56 | const response = await fetch("/score?game=" + game); 57 | const score = await response.json(); 58 | return score; 59 | } 60 | ``` 61 | 62 | No surprises here. 63 | 64 | Let's go modify our `App.jsx`. Put this in there 65 | 66 | ```javascript 67 | import { useState, useEffect } from "react"; 68 | import Score from "./Score"; 69 | import getScore from "./getScore"; 70 | 71 | export default function App() { 72 | const [isPending, setIsPending] = useState(true); 73 | const [game, setGame] = useState(1); 74 | const [score, setScore] = useState({ home: "–", away: "–" }); 75 | 76 | async function getNewScore(game) { 77 | setIsPending(true); 78 | setGame(game); 79 | const newScore = await getScore(game); 80 | setScore(newScore); 81 | setIsPending(false); 82 | } 83 | 84 | useEffect(() => { 85 | getNewScore(game); 86 | }, []); 87 | 88 | return ( 89 |
90 |

Game {game}

91 | 105 |
106 | {" "} 107 | ⚽️ 108 |
109 |
110 | 119 |
120 |
121 | ); 122 | } 123 | ``` 124 | 125 | - So this works, and this is how most people would have coded this - just wait until the API request finishes. 126 | - Why do we need to disable the select while stuff is loading? Because if we don't people can make multiple requests in that time, and they'll return in a jumbled order and it'll freak people out. We have to make sure it finishes it first so we don't have a UI that's responding to old requests. 127 | -------------------------------------------------------------------------------- /lessons/06-transitions/D-making-it-work-with-transitions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Learn how to implement React's useTransition hook to manage UI transitions 4 | effectively during API requests, as taught in Brian Holt's Intermediate React 5 | v6 course for Frontend Masters. 6 | keywords: 7 | - React 8 | - useTransition 9 | - API request 10 | - UI transitions 11 | - Brian Holt 12 | --- 13 | 14 | Luckily the useTransition code is only slightly more complex. Update `App.jsx`: 15 | 16 | ```javascript 17 | // add useTransition at top 18 | import { useState, useTransition, useEffect } from "react"; 19 | 20 | // replace isPending useState 21 | const [isPending, startTransition] = useTransition(); 22 | 23 | // replace getNewScore 24 | async function getNewScore(game) { 25 | setGame(game); 26 | startTransition(async () => { 27 | const newScore = await getScore(game); 28 | startTransition(() => { 29 | setScore(newScore); 30 | }); 31 | }); 32 | } 33 | ``` 34 | 35 | That's it! 36 | 37 | - startTransition gives you back a isPending flag just like we had before. 38 | - It now gives us a function to call that we're starting our UI transition. We're essentially signalling to React that we're starting some sort of transition (in our case, an API request and a subsequent UI render) that we could elect to interrupt with another transition. 39 | - Why two `startTransition` calls? I struggled with this as well. It's in the React docs. It's because in theory React updates aren't instant. If you're a Facebook-sized app, a React update can actually be on the order tens if not hundreds of milliseconds which is forever in terms of code execution, and this means it needs to be captured this way so that the use could in theory interrupt it after the API request has returned but while React is re-rendering. 40 | - I'll say _in this particular case_ it's unnecessary, our app is tiny. But it's to make sure each transition is captured an atomic bit that can be interrupted. 41 | -------------------------------------------------------------------------------- /lessons/06-transitions/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "shuffle" 3 | } 4 | -------------------------------------------------------------------------------- /lessons/07-optimistic-values/A-what-are-optimistic-values.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Discover the concept of optimistic UI updates in messaging apps, where the 4 | user interface reflects message sending before the actual network send, 5 | enhancing user experience. This approach, explained by Brian Holt in the 6 | Intermediate React v6 course, utilizes hooks like useOptimistic and 7 | useTransition to efficiently manage state changes in React applications. 8 | keywords: 9 | - optimistic UI 10 | - React 11 | - Brian Holt 12 | - useOptimistic 13 | - useTransition 14 | --- 15 | 16 | Let's say you are texting your friend. You open iMessage, WhatsApp, Signal, ICQ, AIM, MSN Messenger etc. What is the actual order of operations? 17 | 18 | 1. You type your message 19 | 1. You click send 20 | 1. The message shows up in the log of messages _(this is the one we're interested in)_ 21 | 1. The message is actually sent over the network 22 | 1. The recipient receives it 23 | 24 | That #3 is interesting - you get visual feedback in your log of messages as if the message was already sent. In your mind, the message is sent, and the UI reflects that. In reality the message hasn't even left the device when it's first rendered that way, so in some ways it's a bit misleading, but this **optimistic** display of UI is more closely reflects the user's mental model. 25 | 26 | That's what we're talking about, optimistic UI updates - we're doing some backend work behind the scenes, but we're going to optimistically show the user that their update was done. This is possible to do without the ready-made hook `useOptimistic` but it was such a pain before. Now it's fairly easy to use in conjunction with useTransition (necessary to identify a low priority re-render) to show an intermediary state. 27 | -------------------------------------------------------------------------------- /lessons/07-optimistic-values/B-the-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | Discover how to build a UI in React using server and client interactions 4 | including POST requests with intentional delays for error simulation, as 5 | outlined in Brian Holt's Intermediate React v6 course on Frontend Masters. 6 | Learn to handle UI feedback during asynchronous operations efficiently with 7 | techniques like useOptimisticState, enhancing your web development skills with 8 | JavaScript and server components. 9 | keywords: 10 | - React 11 | - server components 12 | - UI development 13 | - Brian Holt 14 | - Frontend Masters 15 | - JavaScript 16 | - web development 17 | --- 18 | 19 | > You will need to open the folder `optimistic` from the repo in your project. 20 | > 21 | > - [The starter template][starter] 22 | > - [The completed project][completed] 23 | 24 | Let's open the project. 25 | 26 | 1. Open optimistic folder in your editor of choice 27 | 1. Run `npm install` 28 | 1. Glance at server.js - it responds to GET and POST on /thoughts 29 | 1. Run `npm run dev:server` to run the server 30 | 1. Run `npm run dev:client` in another terminal window to run the Vite server 31 | 32 | You'll notice two things here in the server.js file, the DELAY and the ERROR_RATE. I intentionally slowed down the POST request so you can see how the loading state works. Feel free to change how long the delay is (it's in milliseconds). I also wanted you to see what errors look like, so it fails 20% of the time, this is also configurable. 33 | 34 | Okay, let's build a UI that shows users deep thoughts, and allow them to post their own. In App.jsx, put 35 | 36 | ```javascript 37 | import { useEffect, useState } from "react"; 38 | 39 | export default function App() { 40 | const [thoughts, setThoughts] = useState([]); 41 | const [thought, setThought] = useState(""); 42 | 43 | async function postDeepThought() { 44 | setThought(""); 45 | const response = await fetch("/thoughts", { 46 | method: "POST", 47 | headers: { 48 | "Content-Type": "application/json", 49 | }, 50 | body: JSON.stringify({ thought }), 51 | }); 52 | if (!response.ok) { 53 | alert("This thought was not deep enough. Please try again."); 54 | return; 55 | } 56 | const { thoughts: newThoughts } = await response.json(); 57 | setThoughts(newThoughts); 58 | } 59 | 60 | useEffect(() => { 61 | fetch("/thoughts") 62 | .then((res) => res.json()) 63 | .then((data) => { 64 | setThoughts(data); 65 | }); 66 | }, []); 67 | 68 | return ( 69 |
70 |

Deep Thoughts

71 | { 73 | e.preventDefault(); 74 | postDeepThought(); 75 | }} 76 | > 77 | 78 |