As a professional technical editor, I carefully reviewed the draft you submitted.
This article is not just a simple feature introduction, but also talks about "conflicts and solutions based on real experience," and is extremely high-quality content. We rewrote and proofread the book with a focus on the following points, taking advantage of the structure that connects an emotional introduction that readers can easily relate to and a deep technical dive.
1. Unification of writing style and tone: While maintaining professionalism, we maintained the "enthusiastic speaking style" preferred by the developer community.
2. Optimization of technical terminology: Terms such as "Hydration Error" and "HMR" were intentionally left in Katakana/English (maintaining a sense of the field) to improve the context.
3. Structure Reorganization: We have redefined bold text and emphasis to help readers remember important points even when skimming.
4. Technical accuracy note: We have fine-tuned the expressions to match the latest implementations, such as `useActionState` and Fetch API specifications.
Below is the composition of the completed version.
[Deep Dive] Make full use of the latest features of Next.js. A complete record of actual battles using App Router, Server Actions, and Turbopack.
2am. I am writing this article while drinking the last sip of coffee, with only the light from my monitor illuminating the room.
To be honest, the transition period for Next.js (from Pages Router to App Router) was a very bitter experience for an individual developer like me. Confused about the new system, how to keep up with the documentation, and the struggle of wondering, "Which one is the correct answer after all?" However, now that I have actually completed several projects and understand the concept of App Router to the core, I can talk about the intention behind selecting this technology.
This time, I will not just introduce the functions of Next.js's three latest treasures: App Router, Server Actions, and Turbopack, which dramatically changes the development experience.I will write in detail about ``what I learned from actually using them.''
1. App Router and RSC: Embracing the Paradigm Shift
First of all, it's important to note that migrating to App Router is more than just a directory structure change. It is a paradigm shift to a fundamental mechanism called React Server Components (RSC).
In the previous Pages Router, the focus was on how to render the entire page on the client. However, with App Router, it is necessary to design boundaries such as ``How much should be processed by the server and what should be sent to the client?''**
Why is this important?
In conclusion, this is ``to minimize the amount of JavaScript transfer''.In traditional SPA approaches, much of the logic needed to display a page is sent to the client side. However, by using RSC, components that are completed on the server side (data acquisition, complex calculations, etc.) do not need to send JavaScript to the browser. This is the idea behind "Zero Bundle Size," and as a result, Web Vitals such as TBT (Total Blocking Time) and LCP (Largest Contentful Paint) improve dramatically.
What I realized at the implementation site
The first thing I encountered when I started using App Router was how to draw the boundaries for ``use client''. When asked the question, ``To what extent should I make it a client component?'', I initially thought, ``If in doubt, just add `use client`.'' However, this will not maximize the benefits of RSC.My current stance is this. "Basically, it is a server component. Only the parts that require interactive elements (button clicks, form input, etc.) are isolated to the client". Adhering to this design philosophy dramatically improved the balance between code readability and performance.
2. Server Actions: Innovating the development experience by “cutting out” the API route
Next up is Server Actions. This may be one of the features that impressed me most in Next.js development so far.
Until now, when updating data by submitting a form or clicking a button, we dutifully followed the following steps.
1. Hit `fetch('/api/update')` on the client side.
2. Define the API route (`/pages/api/...` or `/app/api/...`).
3. Receive the request on the server side, perform validation, and update the DB.
4. Return the response and update the state on the client side.
Server Actions allows you to skip this intermediate step of "building API routes".
Implementation example: comment posting function
For example, if you want to implement a function for users to post comments, you can use Server Actions to write it using the following structure.// app/actions.ts
'use server'; // This alone guarantees execution on the server side
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
export async function submitComment(formData: FormData) {
const comment = formData.get('comment');
// Validation (simple example)
if (!comment || comment.length === 0) {
throw new Error('Please enter a comment');
}
// Perform direct database operations. There is no need to define API endpoints.
await db.comment.create({
data: {
content: comment as string,
createdAt: new Date(),
},
});
// Re-validate part of the page (update cache)
revalidatePath('/posts/[id]');
}
`
Then, on the front end side, call it like this:
// components/CommentForm.tsx
'use client';
import { submitComment } from '@/app/actions';
import { useFormStatus } from 'react-dom';
export default function CommentForm() {
const { pending } = useFormStatus(); // Get the status of whether it is being sent or not
return (
<form action={submitComment}>
<textarea name="comment" />
<button type="submit" disabled={pending}>
{pending ? 'Sending...' : 'Post'}
</button>
</form>
);
}
`
This is innovative
What's great about this code is that you can write server processing as if you were directly calling a type-safe function. It has excellent compatibility with TypeScript, and structurally prevents bugs (e.g. typos in field names, etc.) caused by discrepancies between the API definition and client-side implementation. Also, by combining it with `useFormStatus` and `useActionState`, loading status and error handling can now be described very intuitively.3. Turbopack: Freeing developers from the stress of waiting time
Apart from digging deep into the technology, Turbopack has to be talked about as a pure "experience."
In the development process, our biggest enemy is ``wait time.''
With Webpack-based build systems, HMR (Hot Module Replacement) becomes sluggish as projects grow, and saving a few seconds of changes can take as long as a sip of coffee. This accumulation significantly reduces the developer's concentration.
Turbopack is trying to solve this by rewriting it in Rust.
Comparison between Webpack and Turbopack
The table below summarizes the differences I felt after actually migrating.| Features | Webpack (Current) | Turbopack (Next) |
|---|---|---|
| Foundation language | JavaScript / TypeScript | Rust / Wasm |
| HMR speed | Decreases in proportion to project size | Very fast (stable even on large scale) |
| Build optimization | Generic dependency resolution | Recompute only incremental changes |
- Turbopack is currently available as standard in Next.js in the development environment, and you can experience the feeling of minimizing the ``lag between saving and applying'' especially in large-scale projects.
4. Barriers encountered in practice and knowledge to overcome them
After reading this far, you may be thinking, ``Isn't everything wonderful?'' However, things are not always smooth sailing in the field. In particular, the following three points are the main challenges faced by developers who are implementing App Router.
① Hydration Error trap
An error that occurs when there is a discrepancy between the HTML generated on the server and the initial rendered result on the client. This is one of the most common problems encountered when migrating to App Router. In particular, if you use `new Date()` or `Math.random()` directly in a component, a Hydration Error will occur due to the difference in execution timing.Countermeasure:
These values must be updated within `useEffect` or isolated in a component wrapped with `dynamic((), { ssr: false })`. This sense of being aware of the boundary between client and server is the key to mastering App Router.
② Data Fetching Strategy
Some people are confused by the removal of the old `getStaticProps` and `getServerSideProps`. However, more flexible and powerful cache control is now possible through extensions to the ``fetch()` API.- `fetch(url, { next: { revalidate: 60 } })`: revalidate every specific number of seconds
- `fetch(url, { cache: 'no-store' })`: Always returns the latest (equivalent to traditional SSR)
- `fetch(url, { next: { revalidate: false } })`: Static generation
③ Prevent Client Component from becoming bloated
It's true that "anything will work if you `use client`," but that diminishes the significance of adopting Next.js. Divide components into the smallest units. For example, if you need a single button interaction, instead of making the entire page or a large section a client, the correct etiquette is to cut out that "button" as a separate file and add `use client` to it.Summary: How will development change in the future?
Taking full advantage of Next.js' latest features is not just about incorporating new features. It is to follow Redefining boundaries in web applications.
- App Router provides a structure to optimize data flow and minimize the amount delivered to users.
- Server Actions removes the barrier between frontend and backend, creating a shortcut for developers to focus on their core logic.
- Turbopack is a powerful engine that reduces developer stress in the process of realizing all of this.
If you're still on the fence about making the transition, I encourage you to take the first step. It may be confusing at first, but once you get used to the idea of this ecosystem, it will be difficult to go back to your old ways.
See you in the next technical memo.