Lượm content từ https://nextjs.org/docs/getting-started và dịch sang tiếng lòng
yarn create next-app --typescript
Directory một page bình thường: pages/posts.js
Dynamic routes: pages/posts/[id].js
Default thì NextJS pre-render tất cả trang -> tốt cho performance và SEO
Mỗi page được tạo HTML riêng với một tí JS, khi html load thì js chạy để trang có thể nhấn nhá được
Static Generation (recommended): tạo sẵn HTML khi yarn build
Server-side rendering: tạo HTML khi request một trang
Nên dùng SG thay vì SSG vì những trang HTML đó có thể cache bằng CDN để tăng performance
Without data thì chả có j để nói
With data thì ta có 2 functions của nextjs để làm việc này
getStaticProps
fetch data bên ngoài và nhúng vào HTML
getStaticPaths
Ví dụ khai báo paths của những bài post thì ta phải fetch data tất cả các ids của các bài post và nextjs sẽ tự tạo đường dẫn. Cùng với content để tạo HTML lúc build
function Post({ post }) {
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch("https://.../posts");
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`);
const post = await res.json();
// Pass post data to the page via props
return { props: { post } };
}
export default Post;
Static Generation thì cần cả getStaticProps
và getStaticPaths
(nếu dynamic routes)
Server-side rendering thì sao, chỉ cần getServerSideProps
, mỗi lần request sẽ chạy hàm này lấy data trước rùi trả về HTML sau, implement y như thằng getStaticProps
ISR giải quyết vấn đề khi bạn có một bài post mới và bạn đang dùng static generation, nó sẽ dò lại xem có trang mới nào không với thời gian là revalidate
giây
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
);
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch("https://.../posts");
const posts = await res.json();
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
};
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
const res = await fetch("https://.../posts");
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: "blocking" };
}
export default Blog;
process.cwd()
import { promises as fs } from "fs";
import path from "path";
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>
<h3>{post.filename}</h3>
<p>{post.content}</p>
</li>
))}
</ul>
);
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), "posts");
const filenames = await fs.readdir(postsDirectory);
const posts = filenames.map(async (filename) => {
const filePath = path.join(postsDirectory, filename);
const fileContents = await fs.readFile(filePath, "utf8");
// Generally you would parse/transform the contents
// For example you can transform markdown to HTML here
return {
filename,
content: fileContents,
};
});
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts: await Promise.all(posts),
},
};
}
export default Blog;
getStaticProps
và getStaticPaths
chỉ runs lúc build
Nên dùng getServerSideProps
khi muốn pre-render cái data để tăng SEO, nếu data quá lớn hay không cần SEO thì chỉ cần fetch ở client thôi. getServerSideProps
chỉ chạy phía server chứ không chạy ở browser!
Dùng preview mode để “preview” trước bài blog/post lúc viết khi integrate với CMS
Còn fetch data ở client side (GET), thì nên dùng thư viện swr, nó xử lý sẵn những thứ như caching, revalidation, tracking, refetching on interval
Chỉ version 10.0.0
Image component từ chính next/image
, nextjs sẽ optimize hình ảnh và default là lazy load luôn, kể cả là image từ src hay external, cực phê
Domains: phải set domain của hình ở file next.config.js
, ngoài ra mình có thể khai báo deviceSizes
để phục responsive hay imageSizes
khi muốn cụ thể size cho ảnh luôn
module.exports = {
images: {
domains: ["example.com"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
Build cả apis trên nextjs được luôn, nó nằm ở pages/api
và được map ở url /api/*
, ví dụ pages/api/user.js
export default function handler(req, res) {
res.status(200).json({ name: "John Doe" });
}
Nhưng nó không có CORS headers nên chỉ gọi ở same-origin mà thôi
Nhìn code cóp pát thôi! :D
Authenticating Statically Generated Pages
// pages/profile.js
import useUser from "../lib/useUser";
import Layout from "../components/Layout";
const Profile = () => {
// Fetch the user client-side
const { user } = useUser({ redirectTo: "/login" });
// Server-render loading state
if (!user || user.isLoggedIn === false) {
return <Layout>Loading...</Layout>;
}
// Once the user request finishes, show the user
return (
<Layout>
<h1>Your Profile</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Layout>
);
};
export default Profile;
Authenticating Server-Rendered Pages
// pages/profile.js
import withSession from "../lib/session";
import Layout from "../components/Layout";
export const getServerSideProps = withSession(async function ({ req, res }) {
// Get the user's session based on the request
const user = req.session.get("user");
if (!user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
return {
props: { user },
};
});
const Profile = ({ user }) => {
// Show the user. No loading state is required
return (
<Layout>
<h1>Your Profile</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Layout>
);
};
export default Profile;