import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }){
const { data, error } = useSWR('/data', fetcher)
if (error) return <div>Error</div>
if (!data) return <div>Loading...</div>
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
Image component and image optimization
The Image
component, imported from next/image
, is an extension of the img
HTML tag designed for the modern web. It includes several built-in optimizations to achieve good Core Web Vitals
performance. These optimizations include the following:
- performance improvement
- ensuring visual stability
- speed up page loading
- ensuring flexibility (scalability) of images
Example of using a local image:
import Image from 'next/image'
import imgSrc from '../public/some-image.png'
export default function Home(){
return (
<>
<h1>Home page</h1>
<Image
src={imgSrc}
alt=""
role="presentation"
/>
</h1 >
)
}
Example of using a remote image (note that you need to set the image width and height):
import Image from 'next/image'
export default function Home(){
return (
<>
<h1>Home page</h1>
<Image
src="/some-image.png"
alt=""
role="presentation"
width={500}
height={500}
/>
</h1 >
)
}
Defining image dimensions
Image
expects to receive the width and height of the image:
- in the case of static import (local image), the width and height are calculated automatically
- width and height can be specified using appropriate props
- if the image dimensions are unknown, you can use the
layout
prop with the fill
value
There are 3 ways to solve the problem of unknown image sizes:
- Using
fill
layout mode: This mode allows you to control the dimensions of the image using the parent element. In this case, the dimensions of the parent element are determined using CSS, and the dimensions of the image are determined using the object-fit
and object-position
properties
- image normalization: if the source of the images is under our control, we can add resizing to the image when it is returned in response to the request
- modification of API calls: the response to a request can include not only the image itself but also its dimensions
Rules for stylizing images:
- choose the right layout mode
- use
className
– it is set to the corresponding img
element. Please note: style
prop is not passed
- when using
layout="fill"
the parent element must have position: relative
- when using
layout="responsive"
the parent element must have display: block
See below for more information about the Image component.
Font optimization
Next.js automatically embeds fonts in CSS at build time:
// before
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
// after
<style data-href="https://fonts.googleapis.com/css2?family=Inter">
@font-face {font-family:'Inter';font-style:normal...}
</style>
To add a font to the page, use the Head
component, imported from next/head
:
// pages/index.js
import Head from 'next/head'
export default function IndexPage(){
return (
<div>
<Head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=optional" />
</Head>
<p>Hello world!</p>
</div>
)
}
To add a font to the application, you need to create a custom document:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDoc extends Document {
render() {
return (
<Html>
<Head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=optional" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
Automatic font optimization can be disabled:
// next.config.js
module.exports = {
optimizeFonts: false
}
For more information about the Head component, see below.
Script Component
The Script
component allows developers to prioritize the loading of third-party scripts, saving time and improving performance.
The script loading priority is determined using the strategy
prop, which takes one of the following values:
beforeInteractive
: This is for important scripts that need to be loaded and executed before the page becomes interactive. Such scripts include, for example, bot detection and permission requests. Such scripts are embedded in the initial HTML and run before the rest of the JS
afterInteractive
: for scripts that can be loaded and executed after the page has become interactive. Such scripts include, for example, tag managers and analytics. Such scripts are executed on the client side and run after hydration
lazyOnload
: for scripts that can be loaded during idle periods. Such scripts include, for example, chat support and social network widgets
Note:
Script
supports built-in scripts with afterInteractive
and lazyOnload
strategies
- inline scripts wrapped in
Script
must have an id
attribute to track and optimize them
Examples
Please note: the Script
component should not be placed inside a Head
component or a custom document.
Loading polyfills:
import Script from 'next/script'
export default function Home(){
return (
<>
<Script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserverEntry%2CIntersectionObserver" strategy="beforeInteractive" />
</>
)
}
Lazy load:
import Script from 'next/script'
export default function Home(){
return (
<>
<Script src="https://connect.facebook.net/en_US/sdk.js"strategy="lazyOnload" />
</>
)
}
Executing code after the page has fully loaded:
import { useState } from 'react'
import Script from 'next/script'
export default function Home(){
const [stripe, setStripe] = useState(null)
return (
<>
<Script
id="stripe-js"
src="https://js.stripe.com/v3/"
onLoad={() => {
setStripe({ stripe: window.Stripe('pk_test_12345') })
}}
/>
</>
)
}
Inline scripts:
import Script from 'next/script'
<Script id="show-banner" strategy="lazyOnload">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
// or
<Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').classList.remove('hidden')`
}}
/>
Passing attributes:
import Script from 'next/script'
export default function Home(){
return (
<>
<Script
src="https://www.google-analytics.com/analytics.js"
id="analytics"
nonce="XUENAJFW"
data-test="analytics"
/>
</>
)
}
Serving static files
Static resources should be placed in the public
directory, located in the root directory of the project. Files located in the public
directory are accessible via the base link /
:
import Image from 'next/image'
export default function Avatar(){
return <Image src="/me.png" alt="me" width="64" height="64" >
}
This directory is also great for storing files such as robots.txt
, favicon.png
, files necessary for Google site verification and other static files (including .html
).
Real-time update
Next.js supports real-time component updates while maintaining local state in most cases (this only applies to functional components and hooks). The state of the component is also preserved when errors (non-rendering related) occur.
To reload a component, just add // @refresh reset
anywhere.
TypeScript
Next.js supports TypeScript out of the box. There are special types GetStaticProps
, GetStaticPaths
and GetServerSideProps
for getStaticProps
, getStaticPaths
and getServerSideProps
:
import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next'
export const getStaticProps: GetStaticProps = async (context) => {
// ...
}
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}
An example of using built-in types for the routing interface (API Routes):
import type{ NextApiRequest, NextApiResponse } from 'next'
export default(req: NextApiRequest, res: NextApiResponse)=> {
res.status(200).json({ message: 'Hello!' })
}
There’s nothing prevents us from typing the data contained in the response:
import type{ NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default(req: NextApiRequest, res: NextApiResponse < Data >)=> {
res.status(200).json({ message: 'Bye!' })
}
Next.js supports paths
and baseUrl
settings defined in tsconfig.json
.
Environment Variables
Next.js has built-in support for environment variables, which allows you to do the following:
use .env.local
to load variables
extrapolate variables to the browser using the NEXT_PUBLIC_
prefix
Assume we have .env.local
file, as below:
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
This will automatically load process.env.DB_HOST
, process.env.DB_USER
and process.env.DB_PASS
into the Node.js runtime
// 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
})
// ...
}
Next.js allows you to use variables inside .env
files:
HOSTNAME=localhost
PORT=8080
HOST=http://$HOSTNAME:$PORT
To pass an environment variable to the browser, you need to add the NEXT_PUBLIC_
prefix to it:
NEXT_PUBLIC_ANALYTICS_ID=value
// pages/index.js
import setupAnalyticsService from '../lib/my-analytics-service'
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() {
return <h1>Hello world!</h1>
}
export default HomePage
In addition to .env.local
, you can create .env
for both modes, .env.development
(for development mode), and .env.production
(for production mode) files. Please note: .env.local
always takes precedence over other files containing environment variables.
This concludes part 2. In part 3 we’ll talk about routing with Next.js – pages, API Routes, layouts, Middleware, and caching.