Next.js คืออะไร? มาเริ่มเขียนเว็บด้วย Next.js กันดีกว่า

Published on
Web Development
2020/03/getting-started-with-nextjs
Discord
บทความสอน Next.js อัพเดทเนื้อหา ปี 2022

สวัสดีครับ บทความนี้จะพาไปรู้จักกับการเขียนเว็บด้วย Next.js กันนะครับ

ซึ่งบทความนี้เป็นบทความเริ่มต้นที่ผมสรุปมาให้อ่านกัน โดยที่ผมไม่ได้เชี่ยวชาญ Next.js นะครับ ตัวผมก็เพิ่งจะได้หัดใช้ Next.js มาไม่นาน 1-2 สัปดาห์ โดยทำโปรเจ็คเล็กๆเท่านั้น เมื่อก่อน เคยได้ยินมานานละ ได้อ่านบล็อกบ้าง คนอื่นๆ เล่าให้ฟังบ้าง แต่ไม่เคยลองจับ ลองเล่นจริงๆ วันนี้เลยลองมาบล็อก และเป็นการทบทวนตัวเองไปในตัวด้วย

สำหรับบทความนี้เนื้อหาเขียนเมื่อปี 2020 ครับ อาจจะมีข้อมูลที่ไม่ได้อัพเดท ตอนนี้ผมได้ทำการเขียนบทความใหม่ เป็นเวอร์ชั่น 2022 สามารถอ่านเพิ่มเติมได้ที่นี่ครับ บทความสอน Next.js อัพเดทเนื้อหา ปี 2022

Next.js คืออะไร?

Next.js เป็น React Web Framework คล้ายๆกัย Create React App ที่ช่วยให้เราเขียนเว็บได้สะดวกขึ้น เพราะเค้า Setup และ Config อะไรหลายๆ อย่างให้เราเรียบร้อยแล้ว เช่น

  • Zero Config - เราไม่ต้อง Config อะไรเลย
  • Ready for Production - พร้อมใช้งาน Production
  • รองรับ SEO เพราะเป็น Server Side Rendering (SSR) (เทียบกับ React ปกติ ส่วนใหญ่จะเป็น Client Side Rendering)
  • มีพวก Code Spliting ให้เลย
  • ทำเป็น Static Site Generator (SSG) ได้ เช่นกัน
  • หรือแม้แต่การทำ Dynamic Page, การทำ APIs หรือการ Custom Server ก็ทำได้เช่นกัน

รวมถึง Documentation อ่านค่อนข้างง่าย มี Example ให้ดูเยอะ เรียกได้ว่าใครที่เคยเขียน React มาก่อน ก็มาเริ่ม หรือใช้ Next.js ไม่ยากเลยครับ

Getting Started

ก่อนเริ่มเลย สิ่งที่ทุกคนควรจะมีคือ

  • พื้นฐาน HTML & CSS & JS เบื้องต้น เคยเขียนเว็บมาบ้าง
  • พื้นฐาน React.js มาบ้าง
  • เนื้อหาเป็น Next.js 9.3 นะครับ (อาจจะมีเนื้อหาของเวอร์ชั่นก่อนหน้าบ้าง)

ถ้าได้แล้วก็ไปลุยกันเลยครับ

โดยก่อนที่จะเริ่ม จริงๆแล้ว ตัว Next.js เค้าก็มี Tutorial มาให้เราลองเรียน เป็น Getting Started เนื้อหาถือว่าครบถ้วนสำหรับผู้เริ่มต้นเลยครับ (เนื้อหาบางส่วนผมก็อ้างอิงจากทีนี่เช่นกัน) มีคำอธิบาย ลงรายละเอียด รวมถึงคำถาม Quiz เล็กๆ น้อยๆ ให้เราตอบ

สร้างโปรเจ็คขึ้นมาใหม่ครับ

npm init -y

จากนั้นติดตั้ง Next.js และ React

npm install react react-dom next

จากนั้น เพิ่มส่วนนี้ลงไปใน package.json

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

ดูให้แน่ใจว่า package.json เราติดตั้ง Next เวอร์ชั่น 9.3 ขึ้นไปนะครับ

เพราะเวลาที่เราจะ Start server ด้วยคำสั่ง npm start หรือ npm run dev จะให้มันรัน คำสั่งของ next นั่นเอง

Create Pages & Routing

ต่อมา ทำการสร้าง Page ขึ้นมา 2 หน้า ครับ คือ Index และ About ซึ่งใน Next.js นั้นมันจะทำ Auto Rouing ให้เราอัตโนมัติโดยไม่ต้องทำอะไรเลย แค่เราสร้าง React Component ไว้ในโฟลเดอร์ชื่อ pages ก็พอ

เช่น ไฟล์ pages/index.js เรามีหน้าตาประมาณนี้

export default () => {
  return (
    <div>
      <h2>This is index page</h2>
    </div>
  );
};

และไฟล์ pages/about.js เป็นแบบนี้

export default () => {
  return (
    <div>
      <h2>This is About page</h2>
    </div>
  );
};

ทีนี้เมื่อเรา start server ขึ้นมา และเข้า url http://localhost:3000 และ http://localhost:3000/about เราก็จะเห็น Index และ About ทันที

การทำ Routing ระหว่าง Page สำหรับ Client Side Routing แบบ React Router ตัว Next ก็มีมาให้เลยคือ next/link โดยหน้าตามัน ก็เป้น Wrapper Component ธรรมดาๆ แบบนี้

<Link href="/about">
  <a>Go to About</a>
</Link>

เราก็สามารถที่จะ Navigate ระหว่างหน้า แบบ React Router ได้เลย

Dynamic Page

เราทำ Routing แล้ว เราก็ทำ Dynamic Page ได้เช่นกัน ตัวอย่างเช่น เราอยากได้ routing กำหนดได้แบบ React Router เช่น

  • blogs/:id - เพื่อแสดงเนื้อหา blog แต่ละบทความ

วิธีการทำคือ เราสร้างไฟล์ ชื่อ [id].js ไว้ใน pages/blogs ครับ

[] จะเป็นการบอก Next.js ว่าให้ทำเป็น Dynamic Route

ในไฟล์นี้มี ข้อมูลแบบนี้

import Link from 'next/link';
import { useRouter } from 'next/router';

export default () => {
  const router = useRouter();
  const { id } = router.query;

  return (
    <div>
      <p>This is blog #{id}</p>
    </div>
  );
};

โดยเมื่อเป็น Dynamic Page ตัว Next.js สามารถอ่าน query.id (ชื่อเดียวกับไฟล์ ที่ตั้งด้วย []) ได้เลยครับ โดยการ access ตัวออปเจ็ค router ก็ใช้ useRouter() เพื่อให้ได้ค่า router นั่นเอง หากใครงง สามารถไปอ่านเรื่อง React Hook เพิ่มเติมได้นะครับ

ทีนี้เวลาเราเข้า url http://localhost:3000/blogs/1 หรือ http://localhost:3000/blogs/12345 เราก็จะได้ content ที่ต่างกันแล้ว

Step ถัดไป เพื่อนๆ ลองนำ id ที่ได้จาก url มาลองสร้างเป็น dynamic จริงๆ เช่น ไปหา id จาก array แล้วเอามาแสดงผล ถ้าไม่เจอ ก็โชว์ว่า not found แล้วกลับไปหน้า Home อะไรพวกนี้ดูครับ

Fetching API

ต่อมา นอกจาก Next.js จะทำเป็น Dynamic Pages ได้แล้ว ตัว Next.js เราก็ยังสามารถที่จะ Fetch APIs ได้ปกติครับ ทั้งจาก API ภายนอก และจาก API ของ Next.js เอง (ที่เดี๋ยวจะพูดในหัวข้อถัดไป)

ซึ่งตัวอย่าง นี้ผมจะทำการดึงข้อมูล meow แบบสุ่มครับ จาก url นี้ https://aws.random.cat/meow

ซึ่งเราจะใช้ความสามารถของ getInitialProps นะครับ

จริงๆแล้ว ใน Next.js 9.3 ที่เพิ่งออกมาเลย วันนี้ เค้าแยก function ออกเป็น getServerSideProps และ getStaticProps เลยครับ เพื่อแยกการ fetch ข้อมูลจาก static page หรือ server page นั่นเอง

ข้อควรรู้ของ getInitialProps คือ ไม่สามารถใช้ใน children component ได้นะครับ ต้องอยู่ใน โฟลเดอร์ pages เท่านั้น

ตัวอย่างการ fetch ข้อมูลแมว ครับ ผมสร้างไฟล์ชื่อ pages/meow.js ดังนี้

import fetch from 'isomorphic-unfetch';

const Meow = ({ file }) => {
  return <img src={file} />;
};

Meow.getInitialProps = async () => {
  const res = await fetch('https://aws.random.cat/meow');
  const data = await res.json();
  return data;
};

export default Meow;

fetch ใช้ isomorphic-unfetch สำหรับ fetch ทั้ง Server/Client นะครับ ติดตั้งด้วย npm install isomorphic-unfetch

API Routes

ใน Next.js เราสามารถทำตัวเป็น Backend APIs ได้เลย โดยที่ไม่ต้องมี Server เพิ่ม มองเป็น Serverless function ก็ได้ครับ โดยเพียงแค่สร้างโฟลเดอร์ api ไว้ภายใน pages ก็พอครับ เช่น

  • pages/api/posts.js - เพื่อเอาไว้ get posts ทั้งหมด โดยเรียกด้วย GET /api/posts
  • pages/api/posts/[id].js - เพื่อดึง post ตาม id มาแสดง โดยเรียกด้วย GET /api/posts/:id

ซึ่งสิ่งสำคัญของไฟล์ API คือ ต้อง export default เป็น Handle function ครับ (ที่มี request และ response) ต่างจากหน้าปกติ ที่ต้องเป็น React Component แบบนี้

export default (req, res) => {
  res.json({
    message: 'OK',
    posts: [{}]
  });
};

ให้มองว่ามันเหมือนกับ Handler ของ Express ครับ

// ซึ่ง handler มันก็เป็น request, response
const handler = (req, res) => ({ posts: [] });

app.get('/posts', handler);

ลองสร้าง API สำหรับ provide ข้อมูล blog posts สร้างไฟล์ชื่อ pages/api/posts.js และ pages/api/posts/[id].js

โดยไฟล์ pages/api/posts.js มีข้อมูลแบบนี้

import posts from '../../mock/posts.js';

export default (req, res) => {
  res.json({
    posts
  });
};

ส่วนไฟล์ pages/api/posts/[id].js เป็นแบบนี้

import posts from '../../../mock/posts.js';

export default (req, res) => {
  const { query } = req;
  const { id } = query;
  // หรือ req.query.id

  // ผมใช้ == เพราะ query id เป็น string แต่ id ใน mock เป็น number.
  // 💡ในการใช้งานจริง ควรไป validation ดีๆนะครับ เพราะ user input อะไรมาก็ได้
  const post = posts.find((post) => post.id == id) || {};

  res.json({
    post
  });
};

อย่าลืมสร้างไฟล์ mock/posts.js ขึ้นมา จริงๆ ใช้พวก faker ก็ได้เช่นกันครับ

export default [
  {
    id: 1,
    title: 'Post #1',
    content: 'lorem ipsum 1'
  },
  {
    id: 2,
    title: 'Post #2',
    content: 'lorem ipsum 2'
  }
];

ทีนี้ API ของเรา สามารถ Access ได้ผ่าน http://localhost:3000/api/posts และ http://localhost:3000/api/posts/:id ได้เลยครับ

ลองเปลี่ยนจาก Fetch API meow มาเป็น API ที่สร้างจาก Next.js ด้วยกันเอง ก็ได้เลยครับ

Running Production

การ Deploy หรือ Run บน Production นั้นก็เหมือน Node.js ปกติเลยครับ หรือเราจะใช้ Hosting ของทาง Zeit ก็ได้เช่นกัน ทีมเดียวกับที่ทำ Next.js นั่นแหละ หรือ Heroku ก็ได้เช่นกันครับ

  • Hosting กับ ZEIT Now
  • Heroku - อาจจะต้องปรับ script start เพราะ Heroku มัน generator port จาก environment เช่น "start": "next start -p $PORT"

หรือถ้าเรารัน VPS เอง หรือ Hosting เอง เราก็แค่รัน

npm run build

เพื่อให้ Next มัน generate production เป็นโฟลเดอร์ .next ขึ้นมา จากนั้นก็สั่ง start ได้เลย

npm start

หรือหากใช้พวก PM2 ก็ start PM2 เหมือน Node.js ปกติได้เลยครับ

pm2 start npm --name "PM2 with Next.js" -- start

Build Static

มาถึงเรื่องของ Buld Static กันครับ ปกติแล้ว Next.js ถ้าเราจะ build static นะครับ ซึ่งมันจะ generate HTML ตอน build time (เรารันด้วยคำสั่ง next build) ซึ่ง Next.js ทำ static html ได้ด้วย การ generate HTML แบบทั้งมี data และไม่ต้องมี data ก็ได้ โดยใช้ getStaticProps ครับ (ตัวก่อน Next.js 9.3 จะใช้แค่ getInitialProps แล้ว Next.js จะไป handle เองว่า เป็น static หรือ server side)

export async function getStaticProps(context) {
  return {
    props: {
      posts: []
    }
  };
}

ลองสร้างไฟล์ pages/meow_static.js ขึ้นมา สำหรับไฟล์นี้ จะ fetch เฉพาะตอน build time ครับ

import fetch from 'isomorphic-unfetch';

const Meow = ({ meow }) => {
  return <img src={meow.file} />;
};

export async function getStaticProps() {
  const res = await fetch('https://aws.random.cat/meow');
  const data = await res.json();

  return {
    props: {
      meow: data || {}
    }
  };
}

export default Meow;

โดย getStaticProps ต้อง export เป็น function และในส่วนของ return { props: {}} จะกลายไปเป็น props ของ Component ทำให้เราสามารถเข้าถึง Meow = ({ meow }) ได้ครับ

เมื่อไหร่ที่ควรใช้ getStaticProps ? และข้อควรรู้

  • เมื่อต้องการ dynamic data ตอน build time
  • getStaticProps จะรันแค่ฝั่ง Server Side ครับ จะไม่รัน client side นั้นหมายความว่า code ส่วนนี้มันจะไม่ถูก bundle ไปด้วย (ซึ่งใน doc บอกเลยไว้ว่า เราสามารถ connect database ตรงนี้ได้เลยนะ )
  • getStaticProps ก็เหมือน getInitialProps คือใช้ได้แค่ใน pages เท่านั้น ไม่สามารถใช้กับ component ที่ไม่ใช่ page ได้
  • ใน development mode ตัว getStaticProps จะรันทุกๆ request ไม่ต้องแปลกใจ

ทีนี้ วิธีการ build static ก็ง่ายๆ เลยครับ ด้วย next export เราอาจจะเพิ่มใน package.json แบบนี้

"scripts": {
  "export": "next export"
}

จากนั้นก็รันด้วยคำสั่ง

npm run build

npm run export

จะได้โฟลเดอร์ out เอาไป server static ได้ปกติเลย

อาจะใช้ Server ก็ได้ครับ

serve out

แล้วเปิด http://localhost:5000/meow_static.html หรือ http://localhost:5000/blogs/1.html

แต่เดี๋ยวก่อน! สังเกตเห็นมั้ยครับ ว่ามีบาง Route / Path ที่มันแสดงผลยังไม่ถูกต้อง เช่น blogs/:id

ถ้าเราต้องการ map พวก export path รวมถึง dynamic route เราต้องเซต exportPathMap ใน next.config.js ด้วยครับ ทำการสร้างไฟล์ next.config.js ที่ Root folder เลย

module.exports = {
  exportTrailingSlash: true,
  exportPathMap: function () {
    return {
      '/': { page: '/' },
      '/blogs/1': { page: '/blogs/[id]' },
      '/blogs/2': { page: '/blogs/[id]' }
    };
  }
};

แล้วลอง build export ใหม่ครับ เป็นอันเรียบร้อย

ซึ่ง เราไม่ได้ Export path ที่เป็น meow หรืออื่นๆ นะครับ ถ้าอยากให้เป็น static ด้วย ก็แค่ไปกำหนด exportPathMap เพิ่มเติม จบปิ๊ง!

สรุป

ตัวอย่างนี้ก็เป็นเพียงแค่ Overview ให้เพื่อนๆ น้องๆได้รู้จักกับ Next.js นะครับ ก็พอจะมองเห็นภาพ มองเห็นแล้วว่า Next.js มันจะทำอะไรต่อได้บ้าง นอกจากนี้ ก็ยังมี Docs หรือ Example ที่ตัว Next.js เค้าทำไว้ให้เราอ่านเข้าใจมากๆ ครับ ไปหาอ่านเพิ่มเติมได้ครับ ส่วนผมเองก็หัดอ่าน และทำตาม Docs Tutorial อยู่เหมือนกันครับ

และถ้าเนื้อหาในบทความนี้ตรงไหนผิดพลาด ก็ขออภัยด้วยนะครับ และส่วนของ Next.js 9.3 เพิ่งมีบล็อออกมาเมื่อ 10 ชั่วโมงที่แล้ว ครับ อ่านได้ที่นี่ครับ

ตัวอย่าง Source Code ที่ใช้ในบทความนี้ครับ

สำหรับบล็อกนี้ก็ขอลากันไปเพียงเท่านี้ เจอกันบล็อกหน้าครับ

❤️ Happy Coding

Buy Me A Coffee
Authors
Discord