Buku Panduan Next.js

Saya menulis tutorial ini untuk membantu anda belajar Next.js dengan cepat dan membiasakannya dengan cara kerjanya.

Ia sangat sesuai untuk anda jika anda mempunyai pengetahuan sifar hingga sedikit tentang Next.js, anda telah menggunakan React pada masa lalu, dan anda berharap dapat menyelami lebih banyak lagi ke dalam ekosistem React, khususnya rendering di sisi pelayan.

Saya dapati Next.js alat yang hebat untuk membuat Aplikasi Web, dan di akhir catatan ini saya harap anda akan teruja dengannya seperti saya. Dan saya harap ia dapat membantu anda belajar Next.js!

Catatan: anda boleh memuat turun versi PDF / ePub / Mobi dari tutorial ini sehingga anda dapat membacanya di luar talian!

Indeks

  1. Pengenalan
  2. Ciri-ciri utama yang disediakan oleh Next.js
  3. Seterusnya.js vs Gatsby vs create-react-app
  4. Cara memasang Next.js
  5. Lihat sumber untuk mengesahkan SSR berfungsi
  6. Aplikasi ini digabungkan
  7. Apakah ikon itu di bahagian bawah kanan?
  8. Pasang React DevTools
  9. Teknik debugging lain yang boleh anda gunakan
  10. Menambah halaman kedua ke laman web
  11. Menghubungkan dua halaman
  12. Kandungan dinamik dengan penghala
  13. Pengambilan awal
  14. Menggunakan penghala untuk mengesan pautan aktif
  15. Menggunakan next/router
  16. Umpan data ke komponen yang menggunakan getInitialProps()
  17. CSS
  18. Mengisi tanda kepala dengan tag tersuai
  19. Menambah komponen pembungkus
  20. Laluan API
  21. Jalankan kod di sisi pelayan, atau di sisi pelanggan
  22. Menyebarkan versi pengeluaran
  23. Menggunakan Sekarang
  24. Menganalisis kumpulan aplikasi
  25. Modul pemuatan malas
  26. Mana boleh pergi dari sini

Pengenalan

Mengusahakan aplikasi JavaScript moden yang dikuasakan oleh React sangat mengagumkan sehingga anda menyedari bahawa terdapat beberapa masalah yang berkaitan dengan membuat semua kandungan di pihak klien.

Pertama, halaman memerlukan waktu lebih lama untuk dapat dilihat oleh pengguna, kerana sebelum konten dimuat, semua JavaScript harus dimuat, dan aplikasi anda perlu dijalankan untuk menentukan apa yang akan ditampilkan di halaman.

Kedua, jika anda membina laman web yang tersedia untuk umum, anda mempunyai masalah kandungan SEO. Mesin pencari menjadi lebih baik dalam menjalankan dan mengindeks aplikasi JavaScript, tetapi lebih baik jika kita dapat mengirimnya kandungan daripada membiarkannya mengetahuinya.

Penyelesaian untuk kedua masalah tersebut adalah rendering pelayan , juga disebut pra-rendering statik .

Next.js adalah satu rangka kerja React untuk melakukan semua ini dengan cara yang sangat mudah, tetapi tidak terhad kepada ini. Ini diiklankan oleh penciptanya sebagai rantai alat satu -konfigurasi, perintah tunggal untuk aplikasi React .

Ini menyediakan struktur umum yang membolehkan anda membuat aplikasi React frontend dengan mudah, dan menangani rendering sisi pelayan untuk anda.

Ciri-ciri utama yang disediakan oleh Next.js

Berikut adalah senarai ciri Next.js yang tidak lengkap:

Muat Naik Kod Panas

Next.js memuat semula halaman apabila mengesan perubahan yang disimpan ke cakera.

Laluan Automatik

URL apa pun dipetakan ke sistem fail, ke fail yang dimasukkan ke dalam pagesfolder, dan anda tidak memerlukan konfigurasi apa pun (tentu saja anda mempunyai pilihan penyesuaian).

Komponen Fail Tunggal

Menggunakan styled-jsx, disatukan sepenuhnya seperti yang dibina oleh pasukan yang sama, sangat mustahak untuk menambahkan gaya yang terdapat pada komponen.

Rendering Pelayan

Anda boleh membuat komponen React di sisi pelayan, sebelum menghantar HTML kepada klien.

Keserasian Ekosistem

Next.js bermain dengan baik dengan sisa ekosistem JavaScript, Node, dan React.

Pemisahan Kod Automatik

Halaman diberikan hanya dengan perpustakaan dan JavaScript yang mereka perlukan, tidak lebih. Daripada menghasilkan satu fail JavaScript tunggal yang mengandungi semua kod aplikasi, aplikasi tersebut dipecah secara automatik oleh Next.js dalam beberapa sumber yang berbeza.

Memuatkan halaman hanya memuatkan JavaScript yang diperlukan untuk halaman tertentu.

Next.js melakukannya dengan menganalisis sumber yang diimport.

Sekiranya hanya salah satu halaman anda yang mengimport perpustakaan Axios, misalnya, halaman khusus itu akan memasukkan perpustakaan dalam kumpulannya.

Ini memastikan pemuatan halaman pertama anda secepat yang mungkin, dan hanya pemuatan halaman yang akan datang (jika ia akan dipicu) akan menghantar JavaScript yang diperlukan kepada klien.

Terdapat satu pengecualian. Import yang sering digunakan dipindahkan ke dalam kumpulan JavaScript utama jika ia digunakan di sekurang-kurangnya separuh halaman laman web.

Pengambilan awal

The Linkkomponen, digunakan untuk menghubungkan halaman bersama-sama berbeza, menyokong prefetchprop yang secara automatik prefetches sumber halaman (termasuk kod hilang kerana kod pemisahan) di latar belakang.

Komponen Dinamik

Anda boleh mengimport modul JavaScript dan React Components secara dinamik.

Eksport Statik

Dengan menggunakan next exportperintah, Next.js membolehkan anda mengeksport laman web sepenuhnya statik dari aplikasi anda.

Sokongan Jenis Skrip

Next.js ditulis dalam TypeScript dan dengan itu dilengkapi dengan sokongan TypeScript yang sangat baik.

Seterusnya.js vs Gatsby vs create-react-app

Next.js, Gatsby, dan create-react-appalat yang luar biasa yang dapat kita gunakan untuk menguatkan aplikasi kita.

Mari kita katakan dahulu kesamaan. Mereka semua mempunyai React di bawah tudung, menguatkan keseluruhan pengalaman pembangunan. Mereka juga memuatkan beg web abstrak dan semua perkara tahap rendah yang biasa kami konfigurasikan secara manual pada masa lalu yang baik.

create-react-apptidak membantu anda menghasilkan aplikasi yang dilayan sisi pelayan dengan mudah. Apa sahaja yang disertakan (SEO, speed ...) hanya disediakan oleh alat seperti Next.js dan Gatsby.

Bilakah Next.js lebih baik daripada Gatsby?

Mereka berdua dapat membantu dengan rendering sisi pelayan , tetapi dengan 2 cara yang berbeza.

Hasil akhirnya menggunakan Gatsby adalah penjana laman web statik, tanpa pelayan. Anda membina laman web, dan kemudian anda menyebarkan hasil proses binaan secara statik di Netlify atau laman web hosting statik yang lain.

Next.js menyediakan backend yang dapat dilayan oleh pihak pelayan sebagai respons terhadap permintaan, membolehkan anda membuat laman web dinamik, yang bermaksud anda akan menyebarkannya pada platform yang dapat menjalankan Node.js.

Next.js boleh menghasilkan laman web statik juga, tetapi saya tidak akan mengatakan bahawa ini adalah kes penggunaan utamanya.

Sekiranya tujuan saya adalah membina laman web statik, saya sukar memilih dan mungkin Gatsby mempunyai ekosistem plugin yang lebih baik, termasuk banyak untuk blogging khususnya.

Gatsby juga sangat berdasarkan GraphQL, sesuatu yang mungkin sangat anda sukai atau tidak suka bergantung pada pendapat dan keperluan anda.

Bagaimana memasang Next.js?

Untuk memasang Next.js, anda perlu memasang Node.js.

Pastikan anda mempunyai versi Node terkini. Periksa dengan berjalan node -vdi terminal anda, dan bandingkan dengan versi LTS terkini yang disenaraikan di //nodejs.org/.

Selepas anda memasang Node.js, anda akan mempunyai npmarahan yang tersedia ke dalam baris arahan anda.

Sekiranya anda menghadapi masalah pada peringkat ini, saya cadangkan tutorial berikut yang saya tulis untuk anda:

  • Cara memasang Node.js
  • Cara mengemas kini Node.js
  • Pengenalan kepada pengurus pakej npm
  • Tutorial Unix Shells
  • Cara menggunakan terminal macOS
  • The Shell Shell

Sekarang anda mempunyai Node, dikemas kini ke versi terbaru, dan npm, kami sudah siap!

Kita boleh memilih 2 laluan sekarang: menggunakan create-next-appatau pendekatan klasik yang melibatkan pemasangan dan penyediaan aplikasi Seterusnya secara manual.

Menggunakan aplikasi create-next

Sekiranya anda biasa create-react-app, create-next-appadalah perkara yang sama - kecuali ia membuat aplikasi Seterusnya dan bukannya aplikasi React, seperti namanya.

Saya menganggap anda sudah memasang Node.js, yang, dari versi 5.2 (2+ tahun yang lalu pada waktu penulisan), disertakan dengan npxperintah yang digabungkan. Alat yang berguna ini membolehkan kami memuat turun dan melaksanakan perintah JavaScript, dan kami akan menggunakannya seperti ini:

npx create-next-app 

Arahan meminta nama permohonan (dan mencipta folder baru untuk anda dengan nama itu), kemudian memuat turun semua pakej yang diperlukan ( react, react-dom, next), menetapkan package.jsonkepada:

dan anda boleh segera menjalankan aplikasi sampel dengan menjalankan npm run dev:

Dan inilah hasilnya di // localhost: 3000:

Ini adalah cara yang disyorkan untuk memulakan aplikasi Next.js, kerana ia memberi anda struktur dan contoh kod untuk dimainkan. Terdapat lebih daripada sekadar aplikasi sampel lalai; anda boleh menggunakan salah satu contoh yang disimpan di //github.com/zeit/next.js/tree/canary/examples menggunakan --examplepilihan. Contohnya cuba:

npx create-next-app --example blog-starter 

Yang memberi anda contoh blog yang dapat segera digunakan dengan sorotan sintaks juga:

Buat aplikasi Next.js secara manual

Anda boleh mengelakkan create-next-appjika anda ingin membuat aplikasi Seterusnya dari awal. Begini caranya: buat folder kosong di mana sahaja yang anda suka, misalnya di folder rumah anda, dan masuk ke dalamnya:

mkdir nextjs cd nextjs 

dan buat direktori projek Seterusnya pertama anda:

mkdir firstproject cd firstproject 

Sekarang gunakan npmarahan untuk memulakannya sebagai projek Node:

npm init -y 

The -ypilihan memberitahu npmuntuk menggunakan tetapan lalai untuk projek, populating sampel package.jsonfail.

Sekarang pasang Next dan React:

npm install next react react-dom 

Folder projek anda kini harus mempunyai 2 fail:

  • package.json (lihat tutorial saya mengenainya)
  • package-lock.json (lihat tutorial saya mengenai kunci pakej)

dan node_modulesfolder.

Buka folder projek menggunakan editor kegemaran anda. Editor kegemaran saya ialah VS Code. Sekiranya anda memasangnya, anda boleh menjalankan code .di terminal anda untuk membuka folder semasa di editor (jika perintah tidak berfungsi untuk anda, lihat ini)

Buka package.json, yang kini mempunyai kandungan ini:

{ "name": "firstproject", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "next": "^9.1.2", "react": "^16.11.0", "react-dom": "^16.11.0" } } 

dan ganti scriptsbahagian dengan:

"scripts": { "dev": "next", "build": "next build", "start": "next start" } 

untuk menambah arahan build Next.js, yang akan kita gunakan tidak lama lagi.

Petua: gunakan "dev": "next -p 3001",untuk menukar port dan jalankan, dalam contoh ini, pada port 3001.

Sekarang buat pagesfolder, dan tambahkan index.jsfail.

Dalam fail ini, mari buat komponen React pertama kami.

Kami akan menggunakannya sebagai eksport lalai:

const Index = () => ( 

Home page

) export default Index

Sekarang menggunakan terminal, jalankan npm run devuntuk memulakan pelayan pembangunan Seterusnya.

Ini akan menjadikan aplikasi tersedia di port 3000, di localhost.

Buka // localhost: 3000 di penyemak imbas anda untuk melihatnya.

Lihat sumber untuk mengesahkan SSR berfungsi

Mari sekarang periksa aplikasi berfungsi seperti yang kita harapkan untuk berfungsi. Ini adalah aplikasi Next.js, jadi ia harus dibuat dari sisi pelayan .

Ini adalah salah satu titik penjualan utama Next.js: jika kita membuat laman web menggunakan Next.js, halaman laman web diberikan pada pelayan, yang menyampaikan HTML ke penyemak imbas.

Ini mempunyai 3 faedah utama:

  • Pelanggan tidak perlu memberi contoh React untuk membuat, yang menjadikan laman web ini lebih cepat kepada pengguna anda.
  • Enjin carian akan mengindeks halaman tanpa perlu menjalankan JavaScript di sisi pelanggan. Sesuatu yang mula dilakukan oleh Google, tetapi secara terbuka mengakui prosesnya lebih perlahan (dan anda harus menolong Google sebanyak mungkin, jika anda ingin mendapat peringkat yang baik).
  • Anda boleh mempunyai tag meta media sosial, berguna untuk menambah gambar pratonton, menyesuaikan tajuk dan keterangan untuk mana-mana halaman anda yang dikongsi di Facebook, Twitter dan sebagainya.

Mari lihat sumber aplikasinya.

Dengan menggunakan Chrome, anda dapat mengklik kanan di mana saja di halaman, dan tekan Lihat Sumber Halaman .

Sekiranya anda melihat sumber halaman, anda akan melihatnya

Home page

coretan dalam HTML body, bersama dengan sekumpulan fail JavaScript - kumpulan aplikasi.

Kami tidak perlu menyiapkan apa-apa, SSR (rendering sisi pelayan) sudah berfungsi untuk kami.

Aplikasi React akan dilancarkan pada pelanggan, dan akan menjadi salah satu interaksi yang kuat seperti mengklik pautan, menggunakan rendering sisi pelanggan. Tetapi memuat semula halaman akan memuatkannya semula dari pelayan. Dan menggunakan Next.js seharusnya tidak ada perbezaan dalam hasil di dalam penyemak imbas - halaman yang diberikan oleh pelayan harus sama persis seperti halaman yang diberikan klien.

Aplikasi ini digabungkan

Semasa melihat sumber halaman, kami melihat banyak fail JavaScript dimuat:

Mari kita mulakan dengan meletakkan kod dalam pemformat HTML agar diformat dengan lebih baik, sehingga kita manusia dapat peluang lebih baik untuk memahaminya:

Home page

{"dataManager":"[]","props":{"pageProps":{}},"page":"/","query":{},"buildId":"development","nextExport":true,"autoExport":true}

Kami memiliki 4 file JavaScript yang dinyatakan akan dimuat sebelumnya head, menggunakan rel="preload" as="script":

  • /_next/static/development/pages/index.js (96 LOC)
  • /_next/static/development/pages/_app.js (5900 LOC)
  • /_next/static/runtime/webpack.js (939 LOC)
  • /_next/static/runtime/main.js (12k LOC)

Ini memberitahu penyemak imbas untuk mula memuat fail tersebut secepat mungkin, sebelum aliran rendering normal bermula. Tanpa itu, skrip akan dimuat dengan penundaan tambahan, dan ini meningkatkan prestasi memuat halaman.

Kemudian 4 fail tersebut dimuat di hujung body, bersama dengan /_next/static/development/dll/dll_01ec57fc9b90d43b98a8.js(31k LOC), dan potongan JSON yang menetapkan beberapa lalai untuk data halaman:

 { "dataManager": "[]", "props": { "pageProps": {} }, "page": "/", "query": {}, "buildId": "development", "nextExport": true, "autoExport": true }  

4 fail bundel yang dimuat sudah menerapkan satu ciri yang disebut pemisahan kod. The index.jsfile menyediakan kod yang diperlukan untuk indexkomponen, yang memberi khidmat kepada /laluan, dan jika kita mempunyai lebih banyak halaman kita akan mempunyai lebih berkas untuk setiap halaman, yang akan kemudian hanya dapat dimuatkan jika diperlukan - untuk menyediakan masa beban yang lebih performant untuk halaman .

Apakah ikon itu di bahagian bawah kanan?

Adakah anda melihat ikon kecil itu di kanan bawah halaman, yang kelihatan seperti kilat?

Sekiranya anda mengarahkannya, akan tertera "Halaman yang Diperolehi":

Ikon ini, yang hanya dapat dilihat dalam mod pengembangan tentu saja, memberitahu anda bahawa halaman memenuhi syarat untuk pengoptimuman statik automatik, yang pada dasarnya bermaksud bahawa ia tidak bergantung pada data yang perlu diambil pada waktu permintaan, dan ia dapat dipraender dan dibuat sebagai fail HTML statik pada masa membina (semasa kita menjalankan npm run build).

Seterusnya dapat menentukan ini dengan tidak adanya getInitialProps()kaedah yang melekat pada komponen halaman.

Apabila ini berlaku, halaman kami akan menjadi lebih cepat kerana laman web akan disajikan secara statik sebagai fail HTML daripada melalui pelayan Node.js yang menghasilkan output HTML.

Another useful icon that might appear next to it, or instead of it on non-prerendered pages, is a little animated triangle:

This is a compilation indicator, and appears when you save a page and Next.js is compiling the application before hot code reloading kicks in to reload the code in the application automatically.

It's a really nice way to immediately determine if the app has already been compiled and you can test a part of it you're working on.

Install the React Developer Tools

Next.js is based on React, so one very useful tool we absolutely need to install (if you haven't already) is the React Developer Tools.

Available for both Chrome and Firefox, the React Developer Tools are an essential instrument you can use to inspect a React application.

Now, the React Developer Tools are not specific to Next.js but I want to introduce them because you might not be 100% familiar with all the tools React provides. It's best to go a little into debugging tooling than assuming you already know them.

They provide an inspector that reveals the React components tree that builds your page, and for each component you can go and check the props, the state, hooks, and lots more.

Once you have installed the React Developer Tools, you can open the regular browser devtools (in Chrome, it's right-click in the page, then click Inspect) and you'll find 2 new panels: Components and Profiler.

If you move the mouse over the components, you'll see that in the page, the browser will select the parts that are rendered by that component.

If you select any component in the tree, the right panel will show you a reference to the parent component, and the props passed to it:

You can easily navigate by clicking around the component names.

You can click the eye icon in the Developer Tools toolbar   to inspect the DOM element, and also if you use the first icon, the one with the mouse icon (which conveniently sits under the similar regular DevTools icon), you can hover an element in the browser UI to directly select the React component that renders it.

You can use the bug icon to log a component data to the console.

This is pretty awesome because once you have the data printed there, you can right-click any element and press "Store as a global variable". For example here I did it with the url prop, and I was able to inspect it in the console using the temporary variable assigned to it, temp1:

Using Source Maps, which are loaded by Next.js automatically in development mode, from the Components panel we can click the code and the DevTools will switch to the Source panel, showing us the component source code:

The Profiler tab is even more awesome, if possible. It allows us to record an interaction in the app, and see what happens. I cannot show an example yet, because it needs at least 2 components to create an interaction, and we have just one now. I'll talk about this later.

I showed all screenshots using Chrome, but the React Developer Tools works in the same way in Firefox:

Other debugging techniques you can use

In addition to the React Developer Tools, which are essential to building a Next.js application, I want to emphasize 2 ways to debug Next.js apps.

The first is obviously console.log() and all the other Console API tools. The way Next apps work will make a log statement work in the browser console OR in the terminal where you started Next using npm run dev.

In particular, if the page loads from the server, when you point the URL to it, or you hit the refresh button / cmd/ctrl-R, any console logging happens in the terminal.

Subsequent page transitions that happen by clicking the mouse will make all console logging happen inside the browser.

Just remember if you are surprised by missing logging.

Another tool that is essential is the debugger statement. Adding this statement to a component will pause the browser rendering the page:

Really awesome because now you can use the browser debugger to inspect values and run your app one line at a time.

You can also use the VS Code debugger to debug server-side code. I mention this technique and this tutorial to set this up.

Adding a second page to the site

Now that we have a good grasp of the tools we can use to help us develop Next.js apps, let's continue from where we left our first app:

I want to add a second page to this website, a blog. It's going to be served into /blog, and for the time being it will just contain a simple static page, just like our first index.js component:

After saving the new file, the npm run dev process already running is already capable of rendering the page, without the need to restart it.

When we hit the URL //localhost:3000/blog we have the new page:

and here's what the terminal told us:

Now the fact that the URL is /blog depends on just the filename, and its position under the pages folder.

You can create a pages/hey/ho page, and that page will show up on the URL //localhost:3000/hey/ho.

What does not matter, for the URL purposes, is the component name inside the file.

Try going and viewing the source of the page, when loaded from the server it will list /_next/static/development/pages/blog.js as one of the bundles loaded, and not /_next/static/development/pages/index.js like in the home page. This is because thanks to automatic code splitting we don't need the bundle that serves the home page. Just the bundle that serves the blog page.

We can also just export an anonymous function from blog.js:

export default () => ( 

Blog

)

or if you prefer the non-arrow function syntax:

export default function() { return ( 

Blog

) }

Linking the two pages

Now that we have 2 pages, defined by index.js and blog.js, we can introduce links.

Normal HTML links within pages are done using the a tag:

Blog 

We can't do do that in Next.js.

Why? We technically can, of course, because this is the Web and on the Web things never break (that's why we can still use the tag. But one of the main benefits of using Next is that once a page is loaded, transitions to other page are very fast thanks to client-side rendering.

If you use a plain a link:

const Index = () => ( 

Home page

Blog ) export default Index

Now open the DevTools, and the Network panel in particular. The first time we load //localhost:3000/ we get all the page bundles loaded:

Now if you click the "Preserve log" button (to avoid clearing the Network panel), and click the "Blog" link, this is what happens:

We got all that JavaScript from the server, again! But.. we don't need all that JavaScript if we already got it. We'd just need the blog.js page bundle, the only one that's new to the page.

To fix this problem, we use a component provided by Next, called Link.

We import it:

import Link from 'next/link' 

and then we use it to wrap our link, like this:

import Link from 'next/link' const Index = () => ( 

Home page

Blog ) export default Index

Now if you retry the thing we did previously, you'll be able to see that only the blog.js bundle is loaded when we move to the blog page:

and the page loaded so faster than before, the browser usual spinner on the tab didn't even appear. Yet the URL changed, as you can see. This is working seamlessly with the browser History API.

This is client-side rendering in action.

What if you now press the back button? Nothing is being loaded, because the browser still has the old index.js bundle in place, ready to load the /index route. It's all automatic!

Dynamic content with the router

In the previous chapter we saw how to link the home to the blog page.

A blog is a great use case for Next.js, one we'll continue to explore in this chapter by adding blog posts.

Blog posts have a dynamic URL. For example a post titled "Hello World" might have the URL /blog/hello-world. A post titled "My second post" might have the URL /blog/my-second-post.

This content is dynamic, and might be taken from a database, markdown files or more.

Next.js can serve dynamic content based on a dynamic URL.

We create a dynamic URL by creating a dynamic page with the [] syntax.

How? We add a pages/blog/[id].js file. This file will handle all the dynamic URLs under the /blog/ route, like the ones we mentioned above: /blog/hello-world, /blog/my-second-post and more.

In the file name, [id] inside the square brackets means that anything that's dynamic will be put inside the id parameter of the query property of the router.

Ok, that's a bit too many things at once.

What's the router?

The router is a library provided by Next.js.

We import it from next/router:

import { useRouter } from 'next/router' 

and once we have useRouter, we instantiate the router object using:

const router = useRouter() 

Once we have this router object, we can extract information from it.

In particular we can get the dynamic part of the URL in the [id].js file by accessing router.query.id.

The dynamic part can also just be a portion of the URL, like post-[id].js.

So let's go on and apply all those things in practice.

Create the file pages/blog/[id].js:

import { useRouter } from 'next/router' export default () => { const router = useRouter() return (  

Blog post

Post id: {router.query.id}

) }

Now if you go to the //localhost:3000/blog/test router, you should see this:

We can use this id parameter to gather the post from a list of posts. From a database, for example. To keep things simple we'll add a posts.json file in the project root folder:

{ "test": { "title": "test post", "content": "Hey some post content" }, "second": { "title": "second post", "content": "Hey this is the second post content" } } 

Now we can import it and lookup the post from the id key:

import { useRouter } from 'next/router' import posts from '../../posts.json' export default () => { const router = useRouter() const post = posts[router.query.id] return (  

{post.title}

{post.content}

) }

Reloading the page should show us this result:

But it's not! Instead, we get an error in the console, and an error in the browser, too:

Why? Because.. during rendering, when the component is initialized, the data is not there yet. We'll see how to provide the data to the component with getInitialProps in the next lesson.

For now, add a little if (!post) return check before returning the JSX:

import { useRouter } from 'next/router' import posts from '../../posts.json' export default () => { const router = useRouter() const post = posts[router.query.id] if (!post) return  return (  

{post.title}

{post.content}

) }

Now things should work. Initially the component is rendered without the dynamic router.query.id information. After rendering, Next.js triggers an update with the query value and the page displays the correct information.

And if you view source, there is that empty

We'll soon fix this issue that fails to implement SSR and this harms both loading times for our users, SEO and social sharing as we already discussed.

We can complete the blog example by listing those posts in pages/blog.js:

import posts from '../posts.json' const Blog = () => ( 

Blog

    {Object.entries(posts).map((value, index) => { return
  • {value[1].title}
  • })}
) export default Blog

And we can link them to the individual post pages, by importing Link from next/link and using it inside the posts loop:

import Link from 'next/link' import posts from '../posts.json' const Blog = () => ( 

Blog

    {Object.entries(posts).map((value, index) => { return (
  • {value[1].title}
  • ) })}
) export default Blog

Prefetching

I mentioned previously how the Link Next.js component can be used to create links between 2 pages, and when you use it, Next.js transparently handles frontend routing for us, so when a user clicks a link, frontend takes care of showing the new page without triggering a new client/server request and response cycle, as it normally happens with web pages.

There's another thing that Next.js does for you when you use Link.

As soon as an element wrapped within appears in the viewport (which means it's visible to the website user), Next.js prefetches the URL it points to, as long as it's a local link (on your website), making the application super fast to the viewer.

This behavior is only being triggered in production mode (we'll talk about this in-depth later), which means you have to stop the application if you are running it with npm run dev, compile your production bundle with npm run build and run it with  npm run start instead.

Using the Network inspector in the DevTools you'll notice that any links above the fold, at page load, start the prefetching as soon as the load event has been fired on your page (triggered when the page is fully loaded, and happens after the DOMContentLoaded event).

Any other Link tag not in the viewport will be prefetched when the user scrolls and it

Prefetching is automatic on high speed connections (Wifi and 3g+ connections, unless the browser sends the Save-Data HTTP Header.

You can opt out from prefetching individual Link instances by setting the prefetch prop to false:

 A link 

Using the router to detect the active link

One very important feature when working with links is determining what is the current URL, and in particular assigning a class to the active link, so we can style it differently from the other ones.

This is especially useful in your site header, for example.

The Next.js default Link component offered in next/link does not do this automatically for us.

We can create a Link component ourselves, and we store it in a file Link.js in the Components folder, and import that instead of the default next/link.

In this component, we'll first import React from react, Link from next/link and the useRouter hook from next/router.

Inside the component we determine if the current path name matches the href prop of the component, and if so we append the selected class to the children.

We finally return this children with the updated class, using React.cloneElement():

import React from 'react' import Link from 'next/link' import { useRouter } from 'next/router' export default ({ href, children }) => { const router = useRouter() let className = children.props.className || '' if (router.pathname === href) { className = `${className} selected` } return {React.cloneElement(children, { className })} } 

Using next/router

We already saw how to use the Link component to declaratively handle routing in Next.js apps.

It's really handy to manage routing in JSX, but sometimes you need to trigger a routing change programmatically.

In this case, you can access the Next.js Router directly, provided in the next/router package, and call its push() method.

Here's an example of accessing the router:

import { useRouter } from 'next/router' export default () => { const router = useRouter() //... } 

Once we get the router object by invoking useRouter(), we can use its methods.

This is the client side router, so methods should only be used in frontend facing code. The easiest way to ensure this is to wrap calls in the useEffect() React hook, or inside componentDidMount() in React stateful components.

The ones you'll likely use the most are push() and prefetch().

push() allows us to programmatically trigger a URL change, in the frontend:

router.push('/login') 

prefetch() allows us to programmatically prefetch a URL, useful when we don't have a Link tag which automatically handles prefetching for us:

router.prefetch('/login') 

Full example:

import { useRouter } from 'next/router' export default () => { const router = useRouter() useEffect(() => { router.prefetch('/login') }) } 

You can also use the router to listen for route change events.

Feed data to the components using getInitialProps

In the previous chapter we had an issue with dynamically generating the post page, because the component required some data up front, and when we tried to get the data from the JSON file:

import { useRouter } from 'next/router' import posts from '../../posts.json' export default () => { const router = useRouter() const post = posts[router.query.id] return ( 

{post.title}

{post.content}

) }

we got this error:

How do we solve this? And how do we make SSR work for dynamic routes?

We must provide the component with props, using a special function called getInitialProps() which is attached to the component.

To do so, first we name the component:

const Post = () => { //... } export default Post 

then we add the function to it:

const Post = () => { //... } Post.getInitialProps = () => { //... } export default Post 

This function gets an object as its argument, which contains several properties. In particular, the thing we are interested into now is that we get the query object, the one we used previously to get the post id.

So we can get it using the object destructuring syntax:

Post.getInitialProps = ({ query }) => { //... } 

Now we can return the post from this function:

Post.getInitialProps = ({ query }) => { return { post: posts[query.id] } } 

And we can also remove the import of useRouter, and we get the post from the props property passed to the Post component:

import posts from '../../posts.json' const Post = props => { return ( 

{props.post.title}

{props.post.content}

) } Post.getInitialProps = ({ query }) => { return { post: posts[query.id] } } export default Post

Now there will be no error, and SSR will be working as expected, as you can see checking view source:

The getInitialProps function will be executed on the server side, but also on the client side, when we navigate to a new page using the Link component as we did.

It's important to note that getInitialProps gets, in the context object it receives, in addition to the query object these other properties:

  • pathname: the path section of URL
  • asPath - String of the actual path (including the query) shows in the browser

which in the case of calling //localhost:3000/blog/test will respectively result to:

  • /blog/[id]
  • /blog/test

And in the case of server side rendering, it will also receive:

  • req: the HTTP request object
  • res: the HTTP response object
  • err: an error object

req and res will be familiar to you if you've done any Node.js coding.

CSS

How do we style React components in Next.js?

We have a lot of freedom, because we can use whatever library we prefer.

But Next.js comes with styled-jsx built-in, because that's a library built by the same people working on Next.js.

And it's a pretty cool library that provides us scoped CSS, which is great for maintainability because the CSS is only affecting the component it's applied to.

I think this is a great approach at writing CSS, without the need to apply additional libraries or preprocessors that add complexity.

To add CSS to a React component in Next.js we insert it inside a snippet in the JSX, which start with

{` 

and ends with

`} 

Inside this weird blocks we write plain CSS, as we'd do in a .css file:

{` h1 { font-size: 3rem; } `} 

You write it inside the JSX, like this:

const Index = () => ( 

Home page

{` h1 { font-size: 3rem; } `} ) export default Index

Inside the block we can use interpolation to dynamically change the values. For example here we assume a size prop is being passed by the parent component, and we use it in the styled-jsx block:

const Index = props => ( 

Home page

{` h1 { font-size: ${props.size}rem; } `} )

If you want to apply some CSS globally, not scoped to a component, you add the global keyword to the style tag:

{` body { margin: 0; } `} 

If you want to import an external CSS file in a Next.js component, you have to first install @zeit/next-css:

npm install @zeit/next-css 

and then create a configuration file in the root of the project, called next.config.js, with this content:

const withCSS = require('@zeit/next-css') module.exports = withCSS() 

After restarting the Next app, you can now import CSS like you normally do with JavaScript libraries or components:

import '../style.css' 

You can also import a SASS file directly, using the @zeit/next-sass library instead.

Populating the head tag with custom tags

From any Next.js page component, you can add information to the page header.

This is handy when:

  • you want to customize the page title
  • you want to change a meta tag

How can you do so?

Inside every component you can import the Head component from next/head and include it in your component JSX output:

import Head from 'next/head' const House = props => ( The page title  {/* the rest of the JSX */} ) export default House 

You can add any HTML tag you'd like to appear in the section of the page.

When mounting the component, Next.js will make sure the tags inside Head are added to the heading of the page. Same when unmounting the component, Next.js will take care of removing those tags.

Adding a wrapper component

All the pages on your site look more or less the same. There's a chrome window, a common base layer, and you just want to change what's inside.

There's a nav bar, a sidebar, and then the actual content.

How do you build such system in Next.js?

There are 2 ways. One is using a Higher Order Component, by creating a components/Layout.js component:

export default Page => { return () => ( 
     
    ....
) }

In there we can import separate components for heading and/or sidebar, and we can also add all the CSS we need.

And you use it in every page like this:

import withLayout from '../components/Layout.js' const Page = () =>

Here's a page!

export default withLayout(Page)

But I found this works only for simple cases, where you don't need to call getInitialProps() on a page.

Why?

Because getInitialProps() gets only called on the page component. But if we export the Higher Order Component withLayout() from a page, Page.getInitialProps() is not called. withLayout.getInitialProps() would.

To avoid unnecessarily complicating our codebase, the alternative approach is to use props:

export default props => ( 
     
    ....
{props.content} )

and in our pages now we use it like this:

import Layout from '../components/Layout.js' const Page = () => ( 
     
      Here's a page!
      

)} /> )

This approach lets us use getInitialProps() from within our page component, with the only downside of having to write the component JSX inside the content prop:

import Layout from '../components/Layout.js' const Page = () => ( 
     
      Here's a page!
      

)} /> ) Page.getInitialProps = ({ query }) => { //... }

API Routes

In addition to creating page routes, which means pages are served to the browser as Web pages, Next.js can create API routes.

This is a very interesting feature because it means that Next.js can be used to create a frontend for data that is stored and retrieved by Next.js itself, transferring JSON via fetch requests.

API routes live under the /pages/api/ folder and are mapped to the /api endpoint.

This feature is very useful when creating applications.

In those routes, we write Node.js code (rather than React code). It's a paradigm shift, you move from the frontend to the backend, but very seamlessly.

Say you have a /pages/api/comments.js file, whose goal is to return the comments of a blog post as JSON.

Say you have a list of comments stored in a comments.json file:

[ { "comment": "First" }, { "comment": "Nice post" } ] 

Here's a sample code, which returns to the client the list of comments:

import comments from './comments.json' export default (req, res) => { res.status(200).json(comments) } 

It will listen on the /api/comments URL for GET requests, and you can try calling it using your browser:

API routes can also use dynamic routing like pages, use the [] syntax to create a dynamic API route, like /pages/api/comments/[id].js which will retrieve the comments specific to a post id.

Inside the [id].js you can retrieve the id value by looking it up inside the req.query object:

import comments from '../comments.json' export default (req, res) => { res.status(200).json({ post: req.query.id, comments }) } 

Heres you can see the above code in action:

In dynamic pages, you'd need to import useRouter from next/router, then get the router object using const router = useRouter(), and then we'd be able to get the id value using router.query.id.

In the server-side it's all easier, as the query is attached to the request object.

If you do a POST request, all works in the same way - it all goes through that default export.

To separate POST from GET and other HTTP methods (PUT, DELETE), lookup the req.method value:

export default (req, res) => { switch (req.method) { case 'GET': //... break case 'POST': //... break default: res.status(405).end() //Method Not Allowed break } } 

In addition to req.query and req.method we already saw, we have access to cookies by referencing req.cookies, the request body in req.body.

Under the hoods, this is all powered by Micro, a library that powers asynchronous HTTP microservices, made by the same team that built Next.js.

You can make use of any Micro middleware in our API routes to add more functionality.

Run code only on the server side or client side

In your page components, you can execute code only in the server-side or on the client-side, by checking the window property.

This property is only existing inside the browser, so you can check

if (typeof window === 'undefined') { } 

and add the server-side code in that block.

Similarly, you can execute client-side code only by checking

if (typeof window !== 'undefined') { } 

JS Tip: We use the typeof operator here because we can't detect a value to be undefined in other ways. We can't do if (window === undefined) because we'd get a "window is not defined" runtime error

Next.js, as a build-time optimization, also removes the code that uses those checks from bundles. A client-side bundle will not include the content wrapped into a if (typeof window === 'undefined') {} block.

Deploying the production version

Deploying an app is always left last in tutorials.

Here I want to introduce it early, just because it's so easy to deploy a Next.js app that we can dive into it now, and then move on to other more complex topics later on.

Remember in the "How to install Next.js" chapter I told you to add those 3 lines to the package.jsonscript section:

"scripts": { "dev": "next", "build": "next build", "start": "next start" } 

We used npm run dev up to now, to call the next command installed locally in node_modules/next/dist/bin/next. This started the development server, which provided us source maps and hot code reloading, two very useful features while debugging.

The same command can be invoked to build the website passing the build flag, by running npm run build. Then, the same command can be used to start the production app passing the start flag, by running npm run start.

Those 2 commands are the ones we must invoke to successfully deploy the production version of our site locally. The production version is highly optimized and does not come with source maps and other things like hot code reloading that would not be beneficial to our end users.

So, let's create a production deploy of our app. Build it using:

npm run build 

The output of the command tells us that some routes (/ and /blog are now prerendered as static HTML, while /blog/[id] will be served by the Node.js backend.

Then you can run npm run start to start the production server locally:

npm run start 

Visiting //localhost:3000 will show us the production version of the app, locally.

Deploying on Now

In the previous chapter we deployed the Next.js application locally.

How do we deploy it to a real web server, so other people can access it?

One of the most simple ways to deploy a Next application is through the Now platform created by Zeit,  the same company that created the Open Source project Next.js. You can use Now to deploy Node.js apps, Static Websites, and much more.

Now makes the deployment and distribution step of an app very, very simple and fast, and in addition to Node.js apps, they also support deploying Go, PHP, Python and other languages.

You can think of it as the "cloud", as you don't really know where your app will be deployed, but you know that you will have a URL where you can reach it.

Now is free to start using, with generous free plan that currently includes 100GB of hosting, 1000 serverless functions invocations per day, 1000 builds per month, 100GB of bandwidth per month, and one CDN location. The pricing page helps get an idea of the costs if you need more.

The best way to start using Now is by using the official Now CLI:

npm install -g now 

Once the command is available, run

now login 

and the app will ask you for your email.

If you haven't registered already, create an account on //zeit.co/signup before continuing, then add your email to the CLI client.

Once this is done, from the Next.js project root folder run

now 

and the app will be instantly deployed to the Now cloud, and you'll be given the unique app URL:

Once you run the now program, the app is deployed to a random URL under the now.sh domain.

We can see 3 different URLs in the output given in the image:

  • //firstproject-2pv7khwwr.now.sh
  • //firstproject-sepia-ten.now.sh
  • //firstproject.flaviocopes.now.sh

Why so many?

The first is the URL identifying the deploy. Every time we deploy the app, this URL will change.

You can test immediately by changing something in the project code, and running now again:

The other 2 URLs will not change. The first is a random one, the second is your project name (which defaults to the current project folder, your account name and then now.sh.

If you visit the URL, you will see the app deployed to production.

You can configure Now to serve the site to your own custom domain or subdomain, but I will not dive into that right now.

The now.sh subdomain is enough for our testing purposes.

Analyzing the app bundles

Next provides us a way to analyze the code bundles that are generated.

Open the package.json file of the app and in the scripts section add those 3 new commands:

"analyze": "cross-env ANALYZE=true next build", "analyze:server": "cross-env BUNDLE_ANALYZE=server next build", "analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build" 

Like this:

{ "name": "firstproject", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next", "build": "next build", "start": "next start", "analyze": "cross-env ANALYZE=true next build", "analyze:server": "cross-env BUNDLE_ANALYZE=server next build", "analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "next": "^9.1.2", "react": "^16.11.0", "react-dom": "^16.11.0" } } 

then install those 2 packages:

npm install --dev cross-env @next/bundle-analyzer 

Create a next.config.js file in the project root, with this content:

const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true' }) module.exports = withBundleAnalyzer({}) 

Now run the command

npm run analyze 

This should open 2 pages in the browser. One for the client bundles, and one for the server bundles:

This is incredibly useful. You can inspect what's taking the most space in the bundles, and you can also use the sidebar to exclude bundles, for an easier visualization of the smaller ones:

Lazy loading modules

Being able to visually analyze a bundle is great because we can optimize our application very easily.

Say we need to load the Moment library in our blog posts. Run:

npm install moment 

to include it in the project.

Now let's simulate the fact we need it on two different routes: /blog and /blog/[id].

We import it in pages/blog/[id].js:

import moment from 'moment' ... const Post = props => { return ( 

{props.post.title}

Published on {moment().format('dddd D MMMM YYYY')}

{props.post.content}

) }

I'm just adding today's date, as an example.

This will include Moment.js in the blog post page bundle, as you can see by running npm run analyze:

See that we now have a red entry in /blog/[id], the route that we added Moment.js to!

It went from ~1kB to 350kB, quite a big deal. And this is because the Moment.js library itself is 349kB.

The client bundles visualization now shows us that the bigger bundle is the page one, which before was very little. And 99% of its code is Moment.js.

Every time we load a blog post we are going to have all this code transferred to the client. Which is not ideal.

One fix would be to look for a library with a smaller size, as Moment.js is not known for being lightweight (especially out of the box with all the locales included), but let's assume for the sake of the example that we must use it.

What we can do instead is separating all the Moment code in a separate bundle.

How? Instead of importing Moment at the component level, we perform an async import inside getInitialProps, and we calculate the value to send to the component.

Remember that we can't return complex objects inside the getInitialProps() returned object, so we calculate the date inside it:

import posts from '../../posts.json' const Post = props => { return ( 

{props.post.title}

Published on {props.date}

{props.post.content}

) } Post.getInitialProps = async ({ query }) => { const moment = (await import('moment')).default() return { date: moment.format('dddd D MMMM YYYY'), post: posts[query.id] } } export default Post

See that special call to .default() after await import? It's needed to reference the default export in a dynamic import (see //v8.dev/features/dynamic-import)

Now if we run npm run analyze again, we can see this:

Our /blog/[id] bundle is again very small, as Moment has been moved to its own bundle file, loaded separately by the browser.

Where to go from here

There is a lot more to know about Next.js. I didn't talk about managing user sessions with login, serverless, managing databases, and so on.

The goal of this Handbook is not to teach you everything, but instead it aims to introduce you, gradually, to all the power of Next.js.

The next step I recommend is to take a good read at the Next.js official documentation to find out more about all the features and functionality I didn't talk about, and take a look at all the additional functionalities introduced by Next.js plugins, some of which are pretty amazing.

You can reach me on Twitter @flaviocopes.

Also check out my website, flaviocopes.com.

Note: you can download a PDF / ePub / Mobi version of this tutorial so you can read it offline!