Part 1 Recording
Part 2 Recording
-
Create a next app:
$ yarn create next-app
-
Run the server:
$ yarn build && yarn start
-
Run in development mode for hot-reloading (SSG will revert to SSR):
$ yarn dev
-
Explore
pages/index.js
:- a simple component
- importing css modules
- modifying
Head
- accessing static content from
public
Any file in pages
is considered a page:
-
The name of the file is the url -
pages/posts.js
will be served to/posts
-
index.js
will be server to/
-
Files in
pages
have to export a react componentpages/posts.js
export default function Posts() { return <div>POSTS PAGE!</div>; }
-
Link
components fromnext/link
can be used to navigate between pages. Important: Thehref
is the path to the file in/pages
not the url
index.js
import Link from "next/link";
export default function Home() {
return (
<div>
<Link href="/posts">Posts</Link>
</div>
);
}
-
Content that should be included on every page (e.g. providers, context, themes, bootstrap, etc) should go in
_app.js
_app.js
import "bootstrap/dist/css/bootstrap.min.css"; function MyApp({ Component, pageProps }) { return ( <> <p> <Link href="/"> <a>pretend</a> </Link>{" "} I'm a navbar with a{" "} <Link href="/posts"> <a>link to posts</a> </Link> </p> <Component {...pageProps} /> </> ); }
index.js
return ( <Link href="/posts"> <div className="btn btn-primary">Posts</div> </Link> );
-
Nested files and folders create compound routes:
| - pages | - index.js | - test.js | - posts | - index.js | - 1.js | - 2.js | - hello | - hi | - index.js | - greeting.js | - how-are-you.js
will server pages at the following urls:
/
/test
/posts
/posts/1
/posts/2
/hello
/hello/hi
/hello/hi/greeting
/hello/how-are-you
(Notice that there isn't a page at
/hello
because there isn't apages/hello/index.js
)pages/posts/index.js
<div> <h1>Posts Page</h1> <div className="row"> <div className="col-3"> <Link href="/posts/1"> <div type="button" className="card m-5"> <div className="card-body"> <h5 className="card-title">Post 1</h5> </div> </div> </Link> </div> </div> </div>
Manually naming our pages isn't always an option.
What if we have pages based on dynamic data?
-
Files and folders in
pages
that have square brackets in the name (e.g.[param].js
are considered dynamic. The parameter can be accessed use theuseRouter
hook./pages/posts/[slug].js
import { useRouter } from "next/router"; export default function Post() { const { slug } = useRouter().query; return <h1>I AM POST {slug}</h1>; }
-
When using a
Link
to navigate to a dynamic route, we need a combination ofhref
andas
:/pages/posts/index.js
// Data const posts = [...]; //[{title, content, slug}] export default function Posts() { const postCards = posts.map((post) => ( <div key={post.slug} className="col-3"> <Link href="/posts/[slug]" as={`/posts/${post.slug}`}> <div type="button" className="card m-5"> <div className="card-body"> <h5 className="card-title">{post.title}</h5> </div> </div> </Link> </div> )); return ( <div> <h1>Posts Page</h1> <div className="row">{postCards}</div> </div> ); }
Next.js attempts to optimize the pre-rendering of the pages being served as much as possible.
If the contents of a page are completely static, it will be served as pure HTML.
Next.js will render as much of the page into pure HTML on the server side.
Any interactive components on the page will be hydrated on the client side after the accompanying JS loads.
When it comes to fetching data in Next.js there are three different strategies that can be adopted depending on your needs.
This is the standard way most react apps render data. After the page is served and after the JS loads, the client (the user's browser) will make the request to fetch the data and hydrate the page. This means the data will be re-requested every time the page is refreshed.
You would use CSR in cases where the data cannot be pre-fetched and is highly dependent on some context on the client-side (like user credentials).
Dashboards, personal calendars, and shopping carts are some examples of the kinds of pages that require CSR.
To use CSR in Next.js the page (or one of its sub components) need to fetch the data.
(Switch to the data-fetching-csr
branch if you want to see an example.)
pages/bootcamps/index.js
import { useState, useEffect } from "react";
import axios from "axios";
// Components
import BootcampList from "../../components/BootcampList";
export default function Bootcamps() {
const [bootcamps, setBootcamps] = useState([]);
const fetchBootcamps = async () => {
const { data } = await axios.get("http://localhost:3001/bootcamps");
setBootcamps(data);
};
useEffect(() => {
fetchBootcamps();
}, []);
return <BootcampList bootcamps={bootcamps} />;
}
pages/bootcamps/[id].js
import { useState, useEffect } from "react";
import axios from "axios";
import { useRouter } from "next/router";
import Error from "next/error";
// Components
import BootcampDetail from "../../components/BootcampDetail";
export default function Bootcamp() {
const { id } = useRouter().query;
const [bootcamp, setBootcamp] = useState(null);
const [loading, setLoading] = useState(true);
const fetchBootcamp = async () => {
try {
const { data } = await axios.get(`http://localhost:3001/bootcamps/${id}`);
setBootcamp(data);
setLoading(false);
} catch (error) {
setLoading(false);
}
};
useEffect(() => {
if (id) fetchBootcamp();
}, [id]);
if (loading) return <h1>Loading...</h1>;
if (!bootcamp) return <Error statusCode={404} />;
return <BootcampDetail bootcamp={bootcamp} />;
}
In server side rendering, the data is fetched on the server, on every request, before the HTML file is served. The client doesn't have to make any requests for the data because the page will load with the data already provided. The page will be fully rendered on the server. The data will be re-requested and the page re-rendered on the server every time the page is refreshed.
You should only use SSR in cases where the data might change between requests and can be fetched independent of the client but it's important for it to be pre-rendered. For example, pre-rendering a shop's public inventory for SEO purposes.
To use SSR in Next.js the page would need to export a getServerSideProps
function. This functions runs on the server every time the page is requested.
(Switch to the data-fetching-ssr
branch if you want to see an example.)
pages/bootcamps/index.js
import axios from "axios";
// Components
import BootcampList from "../../components/BootcampList";
export default function Bootcamps({ bootcamps }) {
return <BootcampList bootcamps={bootcamps} />;
}
export async function getServerSideProps() {
const { data } = await axios.get("http://localhost:3001/bootcamps");
return {
props: {
bootcamps: data,
},
};
}
You can access the parameter in a dynamic route through the context.params
passed to the getServerSideProps
function.
pages/bootcamps/[id].js
import axios from "axios";
import Error from "next/error";
// Components
import BootcampDetail from "../../components/BootcampDetail";
export default function Bootcamp({ bootcamp }) {
if (!bootcamp) return <Error statusCode={404} />;
return <BootcampDetail bootcamp={bootcamp} />;
}
export async function getServerSideProps(context) {
const { id } = context.params;
let bootcamp;
try {
const { data } = await axios.get(`http://localhost:3001/bootcamps/${id}`);
bootcamp = data;
} catch (error) {
console.error(error.message);
}
return { props: { bootcamp } };
}
Static site generation means that the data is fetched once during build time. During the build, Next.js will pre-render the page and generate an HTML file. The client doesn't have to make any requests for the data because the page will load with the data already provided. The page will be fully rendered on the server. The data will not be re-requested even if the client refreshes the page. The state of the content will remain static to when the page was initially built. All users will be served the exact same HTML file. You would need to rebuild the page to show changes to the data.
You should use SSG in cases where the data is public, stable, and will be updated infrequently. Blog posts, shop locations, and courses, are some examples where SSG can be used.
To use SSG in Next.js the page would need to export a getStaticProps
function. This function runs once at build time.
(Switch to the data-fetching-ssg
branch if you want to see an example.)
pages/bootcamps/index.js
import axios from "axios";
// Components
import BootcampList from "../../components/BootcampList";
export default function Bootcamps({ bootcamps }) {
return <BootcampList bootcamps={bootcamps} />;
}
export async function getStaticProps() {
const { data } = await axios.get("http://localhost:3001/bootcamps");
return {
props: {
bootcamps: data,
},
};
}
To use SSG with dynamic routes, you would also need to export getStaticPaths
from the page. This function runs once during the initial build and will generate a static page for each element in the array returned in the paths
key. The array should have a format similar to [{ params: { <PARAM_NAME>: <PARAM_VALUE> }}]
where the <PARAM_NAME>
should match the parameter in the page file name (e.g. /pages/bootcamps/[id].js
expectes an array like [{ params: { id: 1 }}]
).
pages/bootcamps/[id].js
import axios from "axios";
import Error from "next/error";
// Components
import BootcampDetail from "../../components/BootcampDetail";
export default function Bootcamp({ bootcamp }) {
if (!bootcamp) return <Error statusCode={404} />;
return <BootcampDetail bootcamp={bootcamp} />;
}
export async function getStaticPaths() {
const { data } = await axios.get("http://localhost:3001/bootcamps/ids");
const paths = data.map((id) => ({ params: { id } }));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const { data } = await axios.get(
`http://localhost:3001/bootcamps/${params.id}`
);
return { props: { bootcamp: data } };
}