Next.js
Next.js is a flexible full-stack React framework that gives you building blocks to create fast web applications. It solves common problems and makes building React apps easier! 🌐 → https://nextjs.org/
Unlike pure React - Next doesn’t just run in the browser, we need a server for the server-side rendering parts! You can, however, use next export to export to static HTML if you don’t need any of the server features.
API Routes - allow you to build an API that runs on the server
Next.js handles the tooling and configuration needed for React, and provides additional structure, features, and optimizations for your application.
You can use React to build your UI, then incrementally adopt Next.js features to solve common application requirements such as routing, data fetching, integrations - all while improving the developer and end-user experience.
Key Features:
-
File-based Routing (which makes it a bit of an alternative to React Router)
-
Server-side rendering + Data Fetching
Installation
Set it up from a template 🏆
$ npx create-next-app my-next-app --template default
Or go the complicated way and start from scratch…
$ npm install react react-dom next
run via $ npm next dev or add "dev": "next dev" to the scripts section of package.json, then you can do $ npm run dev to the same effect
Pages / Routing
This will continue to work, but they just introduced the app directory as an even better way to do this (see further below).
A page is a react component in the pages directory.
Each page is automatically associated with a route based on its file name.
Example: If you create pages/about.js that exports a React component like below, it will be accessible at /about.
function About() {
return <div>About</div>
}
export default About
Supports dynamic routes! If you create a file called pages/posts/[id].js, then it will be accessible at posts/1, posts/2, etc.
// pages/post/[pid].js
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
The path parameter will be merged with the query parameters, so /post/abc?foo=bar will have the following query object:
{ "foo": "bar", "pid": "abc" }
Catch-all routes: pages/post/[...slug].js matches /post/a, but also /post/a/b, /post/a/b/c and so on.
index > root: The router will automatically route files named index to the root of the directory.
-
pages/index.js→/ -
pages/blog/index.js→/blog
Shallow routing → Docs
Link between pages using the <Link> component:
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About Us</Link>
</li>
<li>
<Link href="/blog/hello-world">Blog Post</Link>
</li>
</ul>
)
}
export default Home
Link to dynamic paths:
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${encodeURIComponent(post.slug)}`}>
{post.title}
</Link>
</li>
))}
</ul>
)
}
export default Posts
app Directory / NEW Router
Pre-rendering Pages
By default, Next.js pre-renders every page. Two ways to choose from:
-
Static Generation (recommended) - HTML is generated **at build-time. **To make a page use Static Generation, either export the page component, or export
getStaticProps(andgetStaticPathsif necessary)-
without data - static page, just renders to one html file
-
with data:
-
Your page content depends on external data: Use
getStaticProps. -
Your page paths depend on external data: Use
getStaticPaths(usually in addition togetStaticProps).
-
-
-
Server-side rendering - HTML is generated on each request
- To use Server-side Rendering for a page, you need to
exportanasyncfunction calledgetServerSideProps. This function will be called by the server on every request.
- To use Server-side Rendering for a page, you need to
-
All this is great, but can cause trouble if you’re not careful! (Unfold for details)
Data Fetching
[SWR]
Built-in CSS Support
Next.js extends the concept of import beyond JavaScript.
Global Stylesheet: will be applied to everything, import inside **pages/_app.js**** **via import '../styles.css' (+ will be hot-reloaded during dev!)
Component-Level CSS: [name].module.css naming schema. CSS Modules locally scope CSS by automatically creating a unique class name. This allows you to use the same CSS class name in different files without worrying about collisions.
- Example
Styled JSX
Styled JSX is an easy way to throw some CSS int he mix → https://github.com/vercel/styled-jsx#global-styles
For example: if you need to change the style of the body element, which is usually the same for every page, so you can’t get to it that easily per page… but just do this in any JSX and you’re good:
<style jsx global>{`
body {
background: #e5e5e5;
}
`}</style>
SASS
SASS support: npm install --save-dev sass, add sassOptions to next.config.js → Docs
Layouts
You can do a single shared layout for the entire app by adding it to pages/_app.js:
// pages/_app.js
import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
pages/_app.js is special. <Component> is always the current page being rendered, so if you wrap your Layout around it, it will always be there.
Or we can do per page layouts like this**:**
// pages/index.js
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
export default function Page() {
return {
/** Your content* /
}
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>)
}
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
Image Optimization
Next.js will automatically determine the width and height of your image based on the imported file.
Local images:
-
Use the
next/image<Image>component:import Image from 'next/image'; -
static-import your image:
import profilePic from '../public/me.png';
import Image from 'next/image'
import profilePic from '../public/me.png'
function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
<p>Welcome to my homepage!</p>
</>
)
}
Remote images:
Next.js does not have access to remote files during the build process, so you'll need to provide the width, height and optional blurDataURL props manually:
import Image from 'next/image'
export default function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
/>
<p>Welcome to my homepage!</p>
</>
)
}
You should add the priority property to the image that will be the Largest Contentful Paint (LCP) element for each page.
Font Optimization
Next inlines font CSS during next build
To add a web font to your Next.js application, add the font to a Custom Document.
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Serve static files
Just put it into the public folder. Then. you can simply do this:
import Image from 'next/image'
function Avatar() {
return <Image src="/me.png" alt="me" width="64" height="64" />
}
export default Avatar
This folder is also useful for robots.txt, favicon.ico, Google Site Verification, and any other static files (including .html)!
Environment Variables
Next.js has built-in support for loading environment variables from .env.local into process.env.
An example .env.local:
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
This loads process.env.DB_HOST, process.env.DB_USER, and process.env.DB_PASS into the Node.js environment automatically allowing you to use them in Next.js data fetching methods and API routes.
For example, using getStaticProps:
// pages/index.js
export async function getStaticProps() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
})
// ...
}
Note: Next.js will automatically expand variables ($VAR) inside of your .env* files. This allows you to reference other secrets, like so:
# .env
HOSTNAME=localhost
PORT=8080
HOST=http://$HOSTNAME:$PORT
API Routes
Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size.
For example, the following API route pages/api/user.js returns a json response with a status code of 200:
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
For an API route to work, you need to export a function as default (a.k.a request handler), which then receives the following parameters:
-
req: An instance of http.IncomingMessage, plus some pre-built middlewares -
res: An instance of http.ServerResponse, plus some helper functions
To handle different HTTP methods in an API route, you can use req.method in your request handler, like so:
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
Dynamic API Routes
Dynamic routes follow the same file naming rules as pages.
For example, the API route pages/api/post/[pid].js has the following code:
export default function handler(req, res) {
const { pid } = req.query
res.end(`Post: ${pid}`)
}
Now, a request to /api/post/abc will respond with the text: Post: abc.
Leave a comment