├── .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 | [](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 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/components/corner.js:
--------------------------------------------------------------------------------
1 | export default function Corner() {
2 | return (
3 |
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 | 
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 | 
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 |
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 |
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 |
70 |
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 |