Closed74

Next13を落ち着いて知る

hajimismhajimism

Next13を使うことになった。Zennの記事とかでさらっと概要は知っていたけど、公式ドキュメントはまだ読んだことがないので落ち着いて読む。

hajimismhajimism

プレスリリース的なやつ
https://nextjs.org/blog/next-13

hajimismhajimism

目次

実は先にapp directoryを触ってみてるのだけれど、less client JS.の部分はゲームチェンジだなと思った。

Turbopackは過大広告だ!みたいな記事を見かけた気がする。いずれにせよなんで速いのか原理は押さえたい。

image/fontの最適化系はこんなんなんぼあってもいいですからね案件

NextLinkにaタグ要らなくなったのかな、嬉しいね

hajimismhajimism

app dirはLayout RFCへのfollow up。
https://nextjs.org/blog/layouts-rfc

Suspence探求のときも思ったけれど、全部親に寄せていきたいReact/Reduxの時代から、どんどん子どもに責務を分散させていく時代へと変化してるのかなーなんて思っている。まだわからんけど。

hajimismhajimism

まあでもLayoutだけでState保てるようになってたりするので必ずしもそうではないか

hajimismhajimism

Support for Data Fetching: async Server Components and extended fetch API enables component-level fetching.

コンポーネントがasync functionになったのはすごいなーって思いました。もともとコンポーネントのメンタルモデルがfunctionであったからいつか出るだろうとは思ってたし、Suspenceの時点でマイクロタスクレベルの非同期処理ができるようになったわけだから実質...みたいなところはあったわけだけども

hajimismhajimism

With Server Components, we're laying the foundations to build complex interfaces while reducing the amount of JavaScript sent to the client, enabling faster initial page loads.

When a route is loaded, the Next.js and React runtime will be loaded, which is cacheable and predictable in size. This runtime does not increase in size as your application grows. Further, the runtime is asynchronously loaded, enabling your HTML from the server to be progressively enhanced on the client.

Next.jsが牽引するフロントエンドの最適化は、基本的に「なるべくSG/SSRしてCDNでキャッシュしとく」という方向性で、これもその姿勢と一貫している。そして開発者の考えることが減るようなAPI設計になっていて、洗練されたな!という印象

hajimismhajimism

With Server Components and nested layouts in Next.js, you're able instantly render parts of the page that do not specifically require data, and show a loading state for parts of the page that are fetching data. With this approach, the user does not have to wait for the entire page to load before they can start interacting with it.

Streamingはまだちょっとよくわかってないんですが、大掛かりなSuspenseをやっているんだと一旦理解

hajimismhajimism

The native fetch Web API has also been extended in React and Next.js. It automatically dedupes fetch requests and provides one flexible way to fetch, cache, and revalidate data at the component level. This means all the benefits of Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR) are now available through one API:

そうそう、これがめっちゃ強力なんすよ。

これめっちゃ重要だな。defaultはSSGなんだ。

// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
fetch(URL, { cache: 'force-cache' });

// This request should be refetched on every request.
// Similar to `getServerSideProps`.
fetch(URL, { cache: 'no-store' });

// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
fetch(URL, { next: { revalidate: 10 } });
hajimismhajimism

We're enabling ergonomic ways to handle loading and error states and stream in UI as it's rendered. In a future release, we'll be improving and simplifying data mutations, as well.

データのやり取りはまだ改善の余地があるらしい。

hajimismhajimism

We're excited to work with the open-source community, package maintainers, and other companies contributing to the React ecosystem to build for this new era of React and Next.js. The ability to colocate data fetching inside components and ship less JavaScript to the client were two important pieces of community feedback we are excited to include with the app/ directory.

「フレームワーク」として素晴らしい進化な気がしました私も。
client componentはReact本体がuseを用意してくれてるわけだし。めっちゃSimpleになってる:tada:

hajimismhajimism

Turbopack only bundles the minimum assets required in development, so startup time is extremely fast. On an application with 3,000 modules, Turbopack takes 1.8 seconds to boot up. Vite takes 11.4 seconds and Webpack takes 16.5 seconds.

速さの秘訣、いまのところ

  • Rustで書かれている
  • 必要最低限しかバンドルしない

しかわからない

hajimismhajimism

Turbopackのドキュメントを覗き見
https://turbo.build/pack/docs/why-turbopack

Frameworks like Vite use a technique where they don’t bundle application source code in development mode. Instead, they rely on the browser’s native ES Modules system. This approach results in incredibly responsive updates since they only have to transform a single file.

However, Vite can hit scaling issues with large applications made up of many modules. A flood of cascading network requests in the browser can lead to a relatively slow startup time. For the browser, it’s faster if it can receive the code it needs in as few network requests as possible - even on a local server.

That’s why we decided that, like Webpack, we wanted Turbopack to bundle the code in the development server. Turbopack can do it much faster, especially for larger applications, because it is written in Rust and skips optimization work that is only necessary for production.

やべ、冒頭ですでによくわからない。これとは別にWebpack/esbuild/Vite/Turbopackを追いかけるスクラップが必要そう。

Other tools take a different attitude to ‘doing less work’. Vite minimizes work done by using Native ESM in development mode. We decided not to take this approach for the reasons listed above.

これはすべてのことに言える、鋭い洞察だな...
コアイメージとしては、並列化とキャッシュをとにかくがんばったということらしい。

hajimismhajimism

あ、ここがコアっぽい

Early versions of Next.js tried to bundle the entire web app in development mode. We quickly realized that this ‘eager’ approach was less than optimal. Modern versions of Next.js bundle only the pages requested by the dev server. For instance, if you go to localhost:3000, it’ll bundle only pages/index.jsx, and the modules it imports.

This more ‘lazy’ approach (only bundling assets when absolutely necessary) is key for a fast dev server. Native ESM handles this without much magic - you request a module, which requests other modules. However, we wanted to build a bundler, for the reasons explained above.

リクエストが来てから必要なモジュールをバンドルする。必要最低限って言ってたのはこのことか。
そんなことしてて色々間に合うんけ?っていのが素人の感想なんですが

hajimismhajimism

Summary

We wanted to:

  • Build a bundler. Bundlers outperform Native ESM when working on large applications.
  • Use incremental computation. The Turbo engine brings this into the core of Turbopack’s architecture - maximizing speed and minimizing work done.
  • Optimize our dev server’s startup time. For that, we build a lazy asset graph to compute only the assets requested.
    That’s why we chose to build Turbopack.

らしい。一旦納得。

hajimismhajimism

もとのドキュメントに戻る。

Hooks
We'll be adding Client and Server component hooks that’ll allow you to access the headers object, cookies, pathnames, search params, etc. In the future, we'll have documentation with more information.

期待。

hajimismhajimism

Interleaving Client and Server Components in a Route

素のReactだとClient ComponentがServer Componentを呼び出せない制限がある。なぜならClient側でServer側でしか手に入らない情報は取り扱えないため。
Nextは全体をServer Componentでラップしておくことで(at Layout層)この制限を気にしなくて良いものとした。

hajimismhajimism

レイアウトではまだ前のデータ取得APIを使うらしい。

export async function getStaticProps() {
  const categories = await getCategoriesFromCMS();

  return {
    props: { categories },
  };
}

export default function BlogLayout({ categories, children }) {
  return (
    <>
      <BlogSidebar categories={categories} />
      {children}
    </>
  );
}
hajimismhajimism

Behavior and priority
Using getStaticProps with revalidate (ISR) in one segment will affect getStaticProps with revalidate in other segments. If there are two revalidate periods in one route, the shorter revalidation will take precedence.

これけっこうむずいかもな

hajimismhajimism

Partial Fetching and Rendering

「同一とみなすことができるものは再計算しない」というReactの基本的な考えをレイアウトに拡張したのだと受け取った

hajimismhajimism

あー!複数のRoot Layoutを作りたいときに使うらしい。たしかにこれほしいと思ってた!

hajimismhajimism

Server-Centric Routing

As users navigate around an app, the router will store the result of the React Server Component payload in an in-memory client-side cache. The cache is split by route segments which allows invalidation at any level and ensures consistency across concurrent renders. This means that for certain cases, the cache of a previously fetched segment can be re-used.

これは賢いすよね

hajimismhajimism

Default loading skeletons

Suspense boundaries will be automatically handled behind-the-scenes with a new file convention called loading.js.

// loading.js
export default function Loading() {
  return <YourSkeleton />
}

// layout.js
export default function Layout({children}) {
  return (
    <>
      <Sidebar />
      {children}
    </>
  )
}

// Output
<>
  <Sidebar />
  <Suspense fallback={<Loading />}>{children}</Suspense>
</>

Sidebarの子どもでfetchingが行われてたらどうなるんだ...?

hajimismhajimism

Templates

Templates are similar to Layouts in that they wrap each child Layout or Page.

Unlike Layouts that persist across routes and maintain state, templates create a new instance for each of their children. This means that when a user navigates between route segments that share a template, a new instance of the component is mounted.

Layoutとは別に、状態を保持しないTemplateなるものがあるらしい

hajimismhajimism

Note: We recommend using Layouts unless you have a specific reason to use a Template.

とのこと

hajimismhajimism

Enter/exit animations など、意図的に再レンダリングしてほしい場所で使うらしい

hajimismhajimism

今後はRouteのインターセプトなんてテクいことも計画しているらしいですね

hajimismhajimism

Dynamic Parallel Routes

これはNested Layoutの尤もらしい使い方だな、むずかしそうだけど

export default function Layout({ views, audience }) {
  return (
    <>
      <div>
        <ViewsNav />
        {views}
      </div>
      <div>
        <AudienceNav />
        {audience}
      </div>
    </>
  );
}
hajimismhajimism

概要はつかめたので次は気になるところを深堀り。まずはData fetchingかな。
https://beta.nextjs.org/docs/data-fetching/fundamentals

hajimismhajimism

Good to know: Previous Next.js data fetching methods such as getServerSideProps, getStaticProps, and getInitialProps are not supported in the new app directory.

全く違うゲームになりました

hajimismhajimism

デフォルトでServer Componentになって、データフェッチングはSuspense的になるべく子ども(データが使われるコンポーネント)でやるのが望ましい。

hajimismhajimism

The new data fetching system is built on top of the native fetch() Web API and makes use of async/await in Server Components.

  • React extends fetch to provide automatic request deduping.
  • Next.js extends the fetch options object to allow each request to set its own caching and revalidating rules.

fetch APIをいじるのどうなん?っていう議論あったけど、ぶっちゃけ使いやすいし全然いいわってなった。

hajimismhajimism

Server Componentの利点

Next.jsは単なるフロントエンドフレームワークを完全に辞めました。

hajimismhajimism

Good to know: For layouts, it's not possible to pass data between a parent layout and its children. We recommend fetching data directly inside the layout that needs it, even if you're requesting the same data multiple times in a route. Behind the scenes, React and Next.js will cache and dedupe requests to avoid the same data being fetched more than once.

ReactやNextがなんかうまいことキャッシュしてくれるので、無理に永続するLayout層でデータフェッチしようと考えなくていいよ、ってことかな

hajimismhajimism

If you need to fetch the same data in multiple components in a tree (e.g. current user), Next.js will automatically cache fetch requests that have the same input in a temporary cache. This optimization is called deduping (or deduplication), and prevents the same data from being fetched more than once during a rendering pass.

いつどこでdedupingされるんだろうか

hajimismhajimism

キャッシュキーを指定しとけばツリー読み込み時に勝手にやってくれるってこと?

hajimismhajimism

The Next.js Cache is a persistent HTTP cache that can be globally distributed. This means the cache can scale automatically and be shared across multiple regions depending on your platform (e.g. Vercel).

そもそもキャッシュそのものについて勉強したくなってきた。次のスクラップ候補。

hajimismhajimism

With Server Components and nested layouts, you're able to instantly render parts of the page that do not specifically require data, and show a loading state for parts of the page that are fetching data. This means the user does not have to wait for the entire page to load before they can start interacting with it.

開発者hはinstantly render partsとそうでない部分を識別して置くことが大事。関連してOptimistic UIとかも学びたいな。

hajimismhajimism
hajimismhajimism

そういえばNext13のディレクトリだとuse使えた

use is a new React function that accepts a promise conceptually similar to await. use handles the promise returned by a function in a way that is compatible with components, hooks, and Suspense. Learn more about use in the React RFC.

hajimismhajimism

Parallel data fetching
最初にPromise.allの外でinitiateするのはなぜ?中に直接書いちゃいけないの?

export default async function Page({ params: { username } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumsData = getArtistAlbums(username);

  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData]);

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  );
}
hajimismhajimism

By starting the fetch prior to calling await in the Server Component, each request can eagerly start to fetch requests at the same time. This sets the components up so you can avoid waterfalls.

らしい。よくわからんけど意味があるっぽい。

hajimismhajimism

UIへの反映を個別に行うためにSuspense境界を設けて子ども側でfetchする。

これはartistのほうが早くレスポンスする前提?それともどっちでも変わらない?
親側のレンダリングが終わってから子供のレンダリングが始まるはずなので、やっぱartistのほうが軽い前提だよね。

export default async function Page({ params: { username } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumData = getArtistAlbums(username);

  // Wait for the artist's promise to resolve first
  const artist = await artistData;

  return (
    <>
      <h1>{artist.name}</h1>
      {/* Send the artist information first,
      and wrap albums in a suspense boundary */}
      <Suspense fallback={<div>Loading...</div>}>
        <Albums promise={albumData} />
      </Suspense>
    </>
  );
}

// Albums Component
async function Albums({ promise }) {
  // Wait for the albums promise to resolve
  const albums = await promise;

  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>{album.name}</li>
      ))}
    </ul>
  );
}
hajimismhajimism

あんま掴みきれた感じしないけど、とりあえずPromise.allを使って親で全部fetchしようとすると全部止まっちゃうので、一部子どもに渡して分けたほうが丁寧なのは理解した。

hajimismhajimism
hajimismhajimism

fetch() has already been patched to include support for cache() automatically, so you don't need to wrap functions that use fetch() with cache(). See automatic request deduping for more information.

リクエストがキャッシュされている仕組みがわかった。便利すぎる。
もうuseとかcacheは本チャン決定でしょ、みたいな雰囲気なんだな

hajimismhajimism

In this new model, we recommend fetching data directly in the component that needs it, even if you're requesting the same data in multiple components instead of prop drilling.

これ何度も出てくる。メンタルモデルの大きな変化だもんね。

hajimismhajimism

preload pattern. 表示がconditionalな場合の最適化テク

うまいことfetchがキャッシュを組んでくれてるおかげで、「Promiseの中身を取り出す前までを予めやっておく」ができるってことかな?

import { getUser } from "@utils/getUser";

export const preload = (id) => {
  void getUser(id);
}
export default async function User({ id }) {
  const result = await getUser(id);
  // ...
}
import User, { preload } from "@components/User";

export default async function Page({ params: { id } }) {
  preload(id); // starting loading the user data now
  const condition = await fetchCondition();
  return condition ? <User id={id} /> : null;
}
hajimismhajimism

import 'server-only' ってなんやろと思ったらここに書いてあった。
サーバー側でしか使えないAPIをクライアントで使おうとしてたときにビルドエラーを吐かせるツールらしい。

https://beta.nextjs.org/docs/rendering/server-and-client-components#keeping-server-only-code-out-of-client-components-poisoning

こういうの用意してる辺り気が利くな〜。今回のNext.jsみたいにクライアントとサーバーの境界線がこれほどまでに曖昧だったことってあるんかな?Vercelさんの開発者体験への熱心さには頭があがらない。

hajimismhajimism
hajimismhajimism

ISRはSSGの更新可能版というより、しばらく保存されるSSRだと思っている

hajimismhajimism

cacheむずかしい

Note: Check if your upstream data provider has caching enabled by default. You might need to disable (e.g. useCdn: false), otherwise a revalidation won't be able to pull fresh data to update the ISR cache. Caching can occur at a CDN (for an endpoint being requested) when it returns the Cache-Control header. ISR on Vercel persists the cache globally and handles rollbacks.

hajimismhajimism

On-demand Revalidation

If you set a revalidate time of 60, all visitors will see the same generated version of your site for one minute. The only way to invalidate the cache is if someone visits the page after the minute has passed.

Starting with v12.2.0, Next.js supports On-Demand Incremental Static Regeneration to manually purge the Next.js cache for a specific page. This makes it easier to update your site when:

  • Content from your headless CMS is created or updated.
  • Ecommerce metadata changes (price, description, category, reviews, etc).

知らなかった。ふつうに強くね?
Jamstack構成のビルド時間問題は終焉していた?

hajimismhajimism
hajimismhajimism

もうちょいいい感じのやつを企てているが、今はrouter.refresh()を使ってくれとのこと。
なんかrouter周りめっちゃ整理されてるんよないつの間にか。

  async function handleChange() {
    setIsFetching(true);
    // Mutate external data source
    await fetch(`https://api.example.com/todo/${todo.id}`, {
      method: 'PUT',
      body: JSON.stringify({ completed: !todo.completed }),
    });
    setIsFetching(false);

    startTransition(() => {
      // Refresh the current route and fetch new data from the server without
      // losing client-side browser or React state.
      router.refresh();
    });
  }
このスクラップは2022/12/23にクローズされました