优化
Image
Image
- next 拓展 image 标签;
- 进行各维度优化;
import Image from "next/image";
本地图片和远程图片
- 本地图片;
- 使用 File 路径;
- 自动计算 width 和 height;
- 远程图片;
- 使用 URL;
- 手动指定 width 和 height;
// 本地图片
<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
/>
// 远程图片
<Image
src="https://s3.amazonaws.com/my-bucket/profile.png"
alt="Picture of the author"
width={500}
height={500}
/>;
安全性
- 手动定义支持的远程图片 URL 模式;
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "s3.amazonaws.com",
port: "",
pathname: "/my-bucket/**",
},
],
},
};
优先级
- 使用 priority 属性;
- 提高图片绘制的优先级;
- 作用于 LCP 机制;
import Image from "next/image";
import profilePic from "../public/me.png";
export default function Page() {
return <Image src={profilePic} alt="Picture of the author" priority />;
}
图片尺寸
- fill 属性:拓展图片尺寸至父标签;
样式
- 使用 className 或者 style 属性调整图片样式;
- 使用 fill 属性时,父标签必须为具有
position: relative
和display: block
;
Font
使用流程
- 导入字体;
- 配置字体;
- 使用字体;
import { Inter } from "next/font/google";
// If loading a variable font, you don't need to specify the font weight
const inter = Inter({
subsets: ["latin"],
display: "swap",
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
多个字体
// fonts.ts
import { Inter, Roboto_Mono } from "next/font/google";
export const inter = Inter({
subsets: ["latin"],
display: "swap",
});
export const roboto_mono = Roboto_Mono({
subsets: ["latin"],
display: "swap",
});
// layout.ts
import { inter } from "./fonts";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.className}>
<body>
<div>{children}</div>
</body>
</html>
);
}
// page.ts
import { roboto_mono } from "./fonts";
export default function Page() {
return (
<>
<h1 className={roboto_mono.className}>My page</h1>
</>
);
}
谷歌字体
- 有衬线:Roboto Serif;
- 无衬线:Roboto Flex;
Script
基础
import Script from "next/script";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
<Script src="https://example.com/script.js" />
</html>
);
}
优化策略
- 当在 layout.ts 中使用 Script 标签;
- 该 layout 对应所有子路由均引用对应脚本且只加载一次;
内联脚本
<Script id="show-banner">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
事件处理程序
- 仅适用于客户端组件;
- onLoad:脚本加载完成后执行;
- onReady:脚本加载完成后或每次加载该组件执行;
- onError:脚本加载失败后执行;
"use client";
import Script from "next/script";
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log("Script has loaded");
}}
onReady={() => {
console.log("Script has readied");
}}
onError={() => {
console.log("Script has error");
}}
/>
</>
);
}
Meta
基础
- 设置 meta 标签;
- 作用于 layout.ts 或 page.ts;
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "...",
description: "...",
};
export default function Page() {}
动态 Meta
- 使用 generateMetadata 动态生成 Meta;
- 仅支持服务器组件;
import type { Metadata, ResolvingMetadata } from "next";
type Props = {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// ...
return {
title: product.title,
openGraph: {
images: ["/some-specific-page-image.jpg", ...previousImages],
},
};
}
export default function Page({ params, searchParams }: Props) {}
行为
默认属性
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
加载机制
- 加载顺序;
- app/layout.tsx (Root Layout);
- app/blog/layout.tsx (Nested Blog Layout);
- app/blog/[slug]/page.tsx (Blog Page);
- 重复的属性将根据加载顺序直接替换;
Public
- 通过 / 引用 public 中的静态文件;
import Image from "next/image";
export function Avatar() {
// .../public/me.png
return <Image src="/me.png" alt="me" width="64" height="64" />;
}
Lazy Loading
懒加载
- 提高首次加载性能;
- 作用于客户端组件;
- 推迟加载客户端组件,第三方库直至用户需要;
- 使用 next/dynamic 或 React.lazy() 实现;
next/dynamic
next/dynamic
- React.lazy() 和 Suspense 的集合体;
ssr
- next/dynamic 默认预渲染;
- 通过
ssr:false
禁用客户端组件的预渲染;
const ComponentC = dynamic(() => import("../components/C"), { ssr: false });
导入服务器组件
- 若动态导入服务器组件;
- 其所有客户端子组件动态导入,不作用于服务器组件本身;
import dynamic from "next/dynamic";
// Server Component:
const ServerComponent = dynamic(() => import("../components/ServerComponent"));
export default function ServerComponentExample() {
return (
<div>
<ServerComponent />
</div>
);
}
动态导入第三方库
- 使用 await import();
"use client";
import { useState } from "react";
export default function Page() {
const [results, setResults] = useState();
return (
<div>
<input
onChange={async (e) => {
const { value } = e.currentTarget;
// Dynamically load fuse.js
const Fuse = (await import("fuse.js")).default;
const fuse = new Fuse();
setResults(fuse.search(value));
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
);
}
动态导入命名组件
- 使用 import(),再其返回的 Promise 中返回对应命名组件;
// hello.ts
"use client";
export function Hello() {
return <p>Hello!</p>;
}
// page.ts
import dynamic from "next/dynamic";
const ClientComponent = dynamic(() =>
import("../components/hello").then((mod) => mod.Hello)
);
客户端组件
最佳实践
Image
响应式图片
import Image from "next/image";
import mountains from "../public/mountains.jpg";
export default function Responsive() {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<Image
alt="Mountains"
// Importing an image will
// automatically set the width and height
src={mountains}
sizes="100vw"
// Make the image display full width
style={{
width: "100%",
height: "auto",
}}
/>
</div>
);
}
填充父标签
import Image from "next/image";
import mountains from "../public/mountains.jpg";
export default function Fill() {
return (
<div
style={{
display: "grid",
gridGap: "8px",
gridTemplateColumns: "repeat(auto-fit, minmax(400px, auto))",
}}
>
<div style={{ position: "relative", height: "400px" }}>
<Image
alt="Mountains"
src={mountains}
fill
sizes="(min-width: 808px) 50vw, 100vw"
style={{
objectFit: "cover", // cover, contain, none
}}
/>
</div>
{/* And more images in the grid... */}
</div>
);
}
背景图片
import Image from "next/image";
import mountains from "../public/mountains.jpg";
export default function Background() {
return (
<Image
alt="Mountains"
src={mountains}
placeholder="blur"
quality={100}
fill
sizes="100vw"
style={{
objectFit: "cover",
}}
/>
);
}