Before you start reading this, I would to note that the text below, although edited and reviewed by me, is written by AI. This is the result of a study session, using the agent as a study partner, talking about new React and NextJS features, as well as using React in this Astro powered site.
The last version of React I used seriously was React 17, usually through Next.js 11. At the time, my default mental model was straightforward: React rendered the application in the browser, hooks managed local behavior, and Next.js supplied routing, data-fetching methods, and server rendering when a page needed it.
That model is not completely wrong today, but it is no longer enough. React and Next.js now ask a more architectural question: which work belongs on the server, which work belongs in the browser, and where should the boundary between them sit?
I am rebuilding this website as a small way to answer that question through practice.
The React 17 model I remember
React 17 applications commonly started from a client entry point and rendered a component tree into a DOM node. In Next.js, pages lived in the pages/ directory, and functions such as getStaticProps or getServerSideProps made the rendering strategy visible at the page level.
Most components were client components in practice. I thought about props, state, effects, context, and performance inside one JavaScript application. Server rendering could produce the first HTML, but the browser still received and hydrated the React tree.
That made the runtime boundary easy to overlook. The component model felt universal even when the framework was doing server work around it.
React 18 changed the foundation
React 18 introduced createRoot, broader automatic batching, transitions, and the concurrent rendering foundation. “Concurrent” does not mean that every component suddenly runs in parallel. The useful idea is that React can prioritize and interrupt rendering work instead of treating every update as equally urgent and indivisible.
startTransition and useTransition make that distinction explicit. An input update should feel immediate, while recalculating a large filtered list can be marked as non-urgent. This is most useful when the interface has enough work for prioritization to matter; it is not something every click handler needs.
Suspense also became more central. I previously associated it mainly with lazy-loaded components. Modern frameworks use it as part of a larger async rendering and streaming model, where the page can reveal useful sections while slower work is still resolving.
The lesson for me is not to sprinkle transitions and Suspense everywhere. It is to recognize that rendering is schedulable work and to use these primitives where the user experience benefits.
React 19 moves mutations closer to the UI model
React 19 adds patterns for async actions and forms, including useActionState, useFormStatus, and useOptimistic. These tools connect a mutation’s pending, success, failure, and optimistic states to the component model.
This can remove repetitive state wiring, but the hard questions remain: what is the source of truth, what can be shown optimistically, how is failure explained, and which layer owns validation? A hook does not make those product decisions for me.
The newer use() API and framework support for server rendering also reinforce the same shift: async data is not merely something fetched in an effect after the page appears. In the right environment, it participates in rendering itself.
Next.js is the bigger mental-model jump
Moving from Next.js 11 to the modern App Router is a larger change than updating a few React APIs.
The app/ directory introduces nested layouts, loading and error boundaries, route handlers, and Server Components by default. A component is no longer assumed to ship to the browser. Interactive boundaries are marked with "use client", and that choice affects the code and data that can cross into the client bundle.
This reverses an old default. Instead of beginning with a browser application and opting into server rendering around it, I can begin with server-rendered or static UI and opt into client execution where interaction requires it.
Caching and revalidation also deserve deliberate study. They are part of the application’s data architecture, not incidental framework magic. Before building a serious modern Next.js application, I want to be able to explain when data is computed, where it is cached, what invalidates it, and what the visitor sees during each state.
Why this site is staying on Astro
Relearning React does not require turning every page into React.
This portfolio is mostly content: an introduction, projects, experience, and articles. Astro is already a good fit because it renders those pages statically, keeps routing and content simple, and sends no client JavaScript unless a component explicitly needs it.
The header controls are my first React island. Theme selection and the mobile menu are genuinely interactive, so the component hydrates immediately with client:load. The article layout, post cards, navigation links, and project descriptions remain Astro because React would not improve them.
This constraint is useful practice. Each island has to justify its browser cost and choose an appropriate hydration strategy:
client:loadfor controls that must work immediately;client:visiblefor interaction that matters only after scrolling into view;client:idlefor enhancements that can wait until the browser is less busy.
The result is not a React single-page application. It is an Astro site with carefully chosen React boundaries.
What I want to practice next
The site gives me a progression that is small enough to understand:
- Keep the theme and navigation island accessible and predictable.
- Add project filtering only when there are enough projects for it to help.
- Add tag filtering after the blog has enough posts.
- Add search only when browsing by title and tag is no longer sufficient.
- Use MDX for an interactive example when an article truly needs one.
Those later islands can exercise typed props, effect cleanup, transitions, optimistic state, and component testing without moving static content into the client bundle.
The updated rule of thumb
My old question was usually, “How should this React component fetch data and manage state?”
The question I want to ask now is broader:
Where should this work happen, and does it need React in the browser at all?
Sometimes the answer will be a Server Component in Next.js. On this site, it will often be an Astro component rendered at build time. When the browser really does need state and interaction, a focused React island is enough.
That feels like the most useful way to return to React: not by rebuilding an old architecture with newer APIs, but by learning the boundaries that modern React frameworks make explicit.

