è¿æ¯ä¸ä»½å¿«éåèå¤å¿åï¼å å« Next.js ç API åèå表åä¸äºç¤ºä¾
npx create-next-app@latest
# or
yarn create next-app
# or
pnpm create next-app
æè å建 TypeScript 项ç®
npx create-next-app@latest --typescript
# or
yarn create next-app --typescript
# or
pnpm create next-app --typescript
è¿è¡ npm run dev æ yarn dev æ pnpm dev ä»¥å¨ http://localhost:3000 ä¸å¯å¨å¼åæå¡å¨
使ç¨ä»¥ä¸å
容填å
pages/index.jsï¼
function HomePage() {
return <div>Welcome to Next.js!</div>
}
export default HomePage
Next.js æ¯å´ç»é¡µé¢çæ¦å¿µæå»ºçã 页颿¯ä» pages ç®å½ä¸ç .jsã.jsxã.ts æ .tsx æä»¶å¯¼åºç React ç»ä»¶
function Page({ data }) {
// æ¸²ææ°æ®...
}
// æ¯ä¸ªè¯·æ±é½ä¼è°ç¨å®
export async function getServerSideProps() {
// ä»å¤é¨ API è·åæ°æ®
const res = await fetch(`https://.../data`)
const data = await res.json()
// éè¿ props å页é¢ä¼ éæ°æ®
return { props: { data } }
}
export default Page
妿æ¨ä»é¡µé¢å¯¼åºä¸ä¸ªå为 getServerSidePropsï¼æå¡å¨ç«¯æ¸²æï¼ç彿°ï¼Next.js å°ä½¿ç¨ getServerSideProps è¿åçæ°æ®å¨æ¯ä¸ªè¯·æ±ä¸é¢æ¸²æè¯¥é¡µé¢
getServerSideProps å¨è¯·æ±æ¶è¿è¡ï¼æ¤é¡µé¢å°ä½¿ç¨è¿åç props è¿è¡é¢æ¸²ænext/link æ next/router å¨å®¢æ·ç«¯é¡µé¢è½¬æ¢ä¸è¯·æ±æ¤é¡µé¢æ¶ï¼Next.js ä¼åæå¡å¨åé API 请æ±ï¼æå¡å¨è¿è¡ getServerSideProps// pages/posts/[id].js
export async function getStaticPaths() {
// å½è¿æ¯ççæ¶ï¼å¨é¢è§ç¯å¢ä¸ï¼ä¸è¦é¢åç°ä»»ä½éæé¡µé¢ï¼æ´å¿«çæå»ºï¼ä½æ´æ
¢çåå§é¡µé¢å è½½ï¼
if (process.env.SKIP_BUILD_STATIC_GENERATION) {
return {
paths: [],
fallback: 'blocking',
}
}
// è°ç¨å¤é¨ API 端ç¹ä»¥è·åå¸å
const res = await fetch('https://.../posts')
const posts = await res.json()
// æ ¹æ®å¸åè·åæä»¬è¦é¢æ¸²æçè·¯å¾ å¨ç产ç¯å¢ä¸ï¼é¢æ¸²æææé¡µé¢
// ï¼æå»ºéåº¦è¾æ
¢ï¼ä½åå§é¡µé¢å è½½é度è¾å¿«ï¼
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// { fallback: false } 表示å
¶ä»è·¯ç±åºè¯¥ 404
return { paths, fallback: false }
}
妿页é¢å
·æå¨æè·¯ç±å¹¶ä½¿ç¨ getStaticPropsï¼åéè¦å®ä¹è¦éæçæçè·¯å¾å表
getStaticProps çæ HTML å JSON æä»¶ï¼è¿ä¸¤ç§æä»¶é½å¯ä»¥ç± CDN ç¼å以æé«æ§è½// å¸åå°å¨æå»ºæ¶ç± getStaticProps() å¡«å
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// è¿ä¸ªå½æ°å¨æå¡å¨ç«¯çæå»ºæ¶è¢«è°ç¨ã
// å®ä¸ä¼å¨å®¢æ·ç«¯è°ç¨ï¼å æ¤æ¨çè³å¯ä»¥ç´æ¥è¿è¡æ°æ®åºæ¥è¯¢ã
export async function getStaticProps() {
// è°ç¨å¤é¨ API 端ç¹ä»¥è·åå¸åã æ¨å¯ä»¥ä½¿ç¨ä»»ä½æ°æ®è·ååº
const res = await fetch('https://.../posts')
const posts = await res.json()
// éè¿è¿å { props: { posts } }ï¼Blog ç»ä»¶å°å¨æå»ºæ¶æ¥æ¶ `posts` ä½ä¸º prop
return {
props: {
posts,
},
}
}
export default Blog
卿å¡å¨ç«¯çæå»ºæ¶è¢«è°ç¨
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// è¿ä¸ªå½æ°å¨æå¡å¨ç«¯çæå»ºæ¶è¢«è°ç¨
// 妿å¯ç¨äºéæ°éªè¯å¹¶ä¸ææ°è¯·æ±è¿å
¥ï¼å®å¯è½ä¼å¨æ æå¡å¨åè½ä¸å次è°ç¨
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js å°å°è¯éæ°çæé¡µé¢ï¼
// - å½è¯·æ±è¿æ¥æ¶
// - æå¤æ¯ 10 ç§ä¸æ¬¡
revalidate: 10, // çå»ä¹é´
}
}
// è¿ä¸ªå½æ°å¨æå¡å¨ç«¯çæå»ºæ¶è¢«è°ç¨
// 妿尿ªçæè·¯å¾ï¼åå¯è½ä¼å¨æ æå¡å¨å½æ°ä¸å次è°ç¨å®
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// æ ¹æ®å¸åè·åæä»¬è¦é¢æ¸²æçè·¯å¾
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// æä»¬å°å¨æå»ºæ¶ä»
颿¸²æè¿äºè·¯å¾
// { fallback: blocking } å¦æè·¯å¾ä¸åå¨ï¼æå¡å¨å°æéåç°é¡µé¢
return { paths, fallback: 'blocking' }
}
export default Blog
import { useState, useEffect } from 'react'
function Profile() {
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
if (isLoading) return <p>Loading...</p>
if (!data) return <p>No profile data</p>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}
import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json())
function Profile() {
const { data, error } = useSWR('/api/profile-data', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}
Next.js å¯ä»¥å¨æ ¹ç®å½ä¸å为 public çæä»¶å¤¹ä¸æä¾éææä»¶ï¼å¦å¾åã ç¶åï¼æ¨ç代ç å¯ä»¥ä»åºæ¬ URL (/) å¼å§å¼ç¨ public ä¸çæä»¶
import Image from 'next/image'
function Avatar() {
return (
<Image
src="/me.png"
alt="me"
width="64"
height="64"
/>
)
}
export default Avatar
Next.js æ¯æé¶é ç½®çç°ä»£æµè§å¨
Next.js æ¯æå¨ package.json æä»¶ä¸é
ç½® Browserslist
{
"browserslist": [
"chrome 64",
"edge 79",
"firefox 67",
"opera 51",
"safari 12"
]
}
妿ä¸åå¨ï¼è¯·å建ä¸ä¸ª pages/_app.js æä»¶ã ç¶åï¼å¯¼å
¥ styles.css æä»¶
import '../styles.css';
// 卿°çâpages/_app.jsâæä»¶ä¸éè¦æ¤é»è®¤å¯¼åº
export default function MyApp({
Component, pageProps
}) {
return <Component {...pageProps} />
}
ä¾å¦ï¼èè以ä¸å为 styles.css çæ ·å¼è¡¨
body {
font-family:
'SF Pro Text', 'SF Pro Icons',
'Helvetica Neue', 'Helvetica',
'Arial', sans-serif;
margin: 0 auto;
}
对äºå
¨å±æ ·å¼è¡¨ï¼å¦ bootstrap æ nprogressï¼æ¨åºè¯¥å¨ pages/_app.js ä¸å¯¼å
¥æä»¶
// pages/_app.js
import 'bootstrap/dist/css/bootstrap.css'
export default function MyApp({
Component, pageProps
}) {
return <Component {...pageProps} />
}
ä» node_modules 导å
¥ CSS æä»¶
æ¨æ éæ
å¿ .error {} ä¸ä»»ä½å
¶ä» .css æ .module.css æä»¶ï¼ä»å°è¢«çæ hash åç§°
.error {
color: white;
background-color: red;
}
ç¶åï¼å建 components/Button.jsï¼å¯¼å
¥å¹¶ä½¿ç¨ä¸é¢ç CSS æä»¶ï¼
import styles from './Button.module.css'
export function Button() {
return (
<button
type="button"
// 请注æâerrorâç±»
// æ¯å¦ä½ä½ä¸ºå¯¼å
¥çâstylesâ对象ç屿§è®¿é®ç
className={styles.error}
>
Destroy
</button>
)
}
Next.js å
许æ¨ä½¿ç¨ .scss å .sass æ©å±å导å
¥ Sassï¼å¯ä»¥éè¿ CSS 模åå .module.scss æ .module.sass æ©å±å使ç¨ç»ä»¶çº§ Sass
$ npm install --save-dev sass
å¨ä½¿ç¨ Next.js çå
ç½® Sass æ¯æä¹åï¼è¯·å¡å¿
sass
éè¿å¨ next.config.js ä¸ä½¿ç¨ sassOptions æ¥å®ç°é
ç½® Sass ç¼è¯å¨ãä¾å¦æ·»å includePathsï¼
const path = require('path')
module.exports = {
sassOptions: {
includePaths:
[path.join(__dirname, 'styles')],
},
}
/* variables.module.scss */
$primary-color: #64ff00;
:export {
primaryColor: $primary-color;
}
å¨ pages/_app.js ä¸å¯¼å
¥ variables.module.scss
import variables from '../styles/variables.module.scss'
export default function MyApp({ Component, pageProps }) {
return (
<Layout color={variables.primaryColor}>
<Component {...pageProps} />
</Layout>
)
}
æç®åçä¸ç§æ¯å èæ ·å¼ï¼
function HiThere() {
return (
<p style={{ color: 'red' }}>hi è¿é</p>
)
}
export default HiThere
ä½¿ç¨ styled-jsx çç»ä»¶å¦ä¸æç¤ºï¼
function HelloWorld() {
return (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p { color: blue; }
div { background: red; }
@media (max-width: 600px) {
div { background: blue; }
}
`}</style>
<style global jsx>{`
body { background: black; }
`}</style>
</div>
)
}
export default HelloWorld
å½ç¶ï¼ä½ ä¹å¯ä»¥ä½¿ç¨ styled-components
// components/layout.js
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
// pages/_app.js
import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
// pages/index.tsx
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'
const Page: NextPageWithLayout = () => {
return <p>hello world</p>
}
Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
export default Page
// pages/_app.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// 使ç¨å¨é¡µé¢çº§å«å®ä¹çå¸å±ï¼å¦æå¯ç¨ï¼
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
// 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 }) {
// 使ç¨å¨é¡µé¢çº§å«å®ä¹çå¸å±ï¼å¦æå¯ç¨ï¼
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
// components/layout.js
import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }) {
const { data, error } = useSWR('/api/navigation', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
return (
<>
<Navbar links={data.links} />
<main>{children}</main>
<Footer />
</>
)
}
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} èªå¨æä¾
// height={500} èªå¨æä¾
// blurDataURL="data:..." èªå¨æä¾
// placeholder="blur" // å è½½æ¶å¯éçæ¨¡ç³å¤ç
/>
<p>Welcome to my homepage!</p>
</>
)
}
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>
</>
)
}
è¦ä½¿ç¨è¿ç¨å¾åï¼src 屿§åºè¯¥æ¯ä¸ä¸ª URL å符串ï¼å¯ä»¥æ¯ç¸å¯¹çä¹å¯ä»¥æ¯ç»å¯¹ç
æ¨åºè¯¥å°ä¼å çº§å±æ§æ·»å å°å°æä¸ºæ¯ä¸ªé¡µé¢ç Largest Contentful Paint (LCP) å ç´ çå¾åã è¿æ ·åå 许 Next.js ä¸é¨ç¡®å®è¦å è½½çå¾åçä¼å 级ï¼ä¾å¦ï¼éè¿é¢å è½½æ ç¾æä¼å 级æç¤ºï¼ï¼ä»èæ¾çæé« LCP
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}
priority
/>
<p>Welcome to my homepage!</p>
</>
)
}
èªå¨æç®¡ä»»ä½ Google åä½ã åä½å å«å¨é¨ç½²ä¸ï¼å¹¶ä»ä¸æ¨çé¨ç½²ç¸åçåæä¾æå¡ã æµè§å¨ä¸ä¼å Google åéä»»ä½è¯·æ±
// pages/_app.js
import { Inter } from '@next/font/google'
// 妿å è½½å¯ååä½ï¼åæ éæå®åä½ç²ç»
const inter = Inter({ subsets: ['latin'] })
export default function MyApp({
Component, pageProps
}) {
return (
<main className={inter.className}>
<Component {...pageProps} />
</main>
)
}
妿ä¸è½ä½¿ç¨å¯ååä½ï¼åéè¦æå®ç²ç»ï¼
// pages/_app.js
import { Roboto } from '@next/font/google'
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
})
export default function MyApp({
Component, pageProps
}) {
return (
<main className={roboto.className}>
<Component {...pageProps} />
</main>
)
}
const roboto = Roboto({
weight: ['400', '700'],
style: ['normal', 'italic'],
subsets: ['latin'],
})
// pages/_app.js
import { Inter } from '@next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function MyApp({ Component, pageProps }) {
return (
<>
<style jsx global>{`
html {
font-family: ${inter.style.fontFamily};
}
`}</style>
<Component {...pageProps} />
</>
)
}
// pages/index.js
import { Inter } from '@next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function Home() {
return (
<div className={inter.className}>
<p>Hello World</p>
</div>
)
}
// pages/_app.js
const inter = Inter({ subsets: ['latin'] })
å¨ next.config.js ä¸å
¨å±ä½¿ç¨ææåä½
// next.config.js
module.exports = {
experimental: {
fontLoaders: [
{
loader: '@next/font/google',
options: { subsets: ['latin'] }
},
],
},
}
妿䏤è é½é ç½®ï¼å使ç¨å½æ°è°ç¨ä¸çåé
// pages/_app.js
import localFont from '@next/font/local'
// å使件å¯ä»¥ä½äºâpagesâå
const myFont = localFont({
src: './my-font.woff2'
})
export default function MyApp({
Component, pageProps
}) {
return (
<main className={myFont.className}>
<Component {...pageProps} />
</main>
)
}
妿è¦ä¸ºå个åä½ç³»å使ç¨å¤ä¸ªæä»¶ï¼src å¯ä»¥æ¯ä¸ä¸ªæ°ç»ï¼
const roboto = localFont({
src: [
{
path: './Roboto-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './Roboto-Italic.woff2',
weight: '400',
style: 'italic',
},
{
path: './Roboto-Bold.woff2',
weight: '700',
style: 'normal',
},
{
path: './Roboto-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
],
})
// pages/_app.js
import { Inter } from '@next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
});
export default function MyApp({ Component, pageProps }) {
return (
<main className={`${inter.variable} font-sans`}>
<Component {...pageProps} />
</main>
)
}
æåï¼å° CSS åéæ·»å å°æ¨ç Tailwind CSS é ç½®ä¸ï¼
// tailwind.config.js
const { fontFamily } = require('tailwindcss/defaultTheme')
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', ...fontFamily.sans],
},
},
},
plugins: [],
}
import Script from 'next/script'
export default function Dashboard() {
return (
<>
<Script
src="https://example.com/script.js"
/>
</>
)
}
è¦ä¸ºææè·¯ç±å è½½ç¬¬ä¸æ¹èæ¬ï¼å¯¼å
¥ next/script å¹¶å°èæ¬ç´æ¥å
å«å¨ pages/_app.js ä¸
import Script from 'next/script'
export default function MyApp({
Component, pageProps
}) {
return (
<>
<Script
src="https://example.com/script.js"
/>
<Component {...pageProps} />
</>
)
}
æ¤çç¥ä»å¤äºè¯éªé¶æ®µï¼åªæå¨ next.config.js ä¸å¯ç¨äº nextScriptWorkers æ å¿æ¶æè½ä½¿ç¨ï¼
module.exports = {
experimental: {
nextScriptWorkers: true,
},
}
è®¾ç½®å®æåï¼å®ä¹ strategy="worker" å°èªå¨å¨æ¨çåºç¨ç¨åºä¸å®ä¾å Partytown å¹¶å°èæ¬å¸è½½å°ç½ç»å·¥ä½è
import Script from 'next/script'
export default function Home() {
return (
<>
<Script
src="https://example.com/script.js"
strategy="worker"
/>
</>
)
}
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
id="example-script"
nonce="XUENAJFW"
data-test="script"
/>
</>
)
}
<Script id="show-banner">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
<Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').classList.remove('hidden')`,
}}
/>
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
"scripts": {
"lint": "next lint"
}
ç¶åè¿è¡ npm run lint æ yarn lintï¼
yarn lint
# ä½ ä¼çå°è¿æ ·çæç¤ºï¼
#
# ? æ¨æ³å¦ä½é
ç½® ESLintï¼
#
# â¯ åºæ¬é
ç½® + Core Web Vitals è§åéï¼æ¨èï¼
# åºæ¬é
ç½®
# None
Strict ä¸¥æ ¼é ç½®ï¼å æ¬ Next.js çåºæ¬ ESLint é ç½®ä»¥åæ´ä¸¥æ ¼ç Core Web Vitals è§åé
{
"extends": "next/core-web-vitals"
}
Base åºç¡é ç½®ï¼å æ¬ Next.js çåºæ¬ ESLint é ç½®
{
"extends": "next"
}
项ç®çæ ¹ç®å½ä¸å建ä¸ä¸ªå
嫿éé
ç½®ç .eslintrc.json æä»¶
{
"extends": "next",
"settings": {
"next": {
"rootDir": "packages/my-app/"
}
}
}
rootDir å¯ä»¥æ¯è·¯å¾ï¼ç¸å¯¹æç»å¯¹ï¼ãglobï¼å³âpackages/*/âï¼æè·¯å¾å/æ glob æ°ç»
module.exports = {
eslint: {
dirs: ['pages', 'utils'],
},
}
å¨ç产æå»ºæé´ï¼next buildï¼ä»
å¨âpagesâåâutilsâç®å½ä¸è¿è¡ ESLintï¼æè
使ç¨å½ä»¤
$ next lint --dir pages --dir utils --file bar.js
æ¨å¯ä»¥ä½¿ç¨ .eslintrc ä¸ç rules 屿§ç´æ¥æ´æ¹å®ä»¬ï¼
{
"extends": "next",
"rules": {
"react/no-unescaped-entities": "off",
"@next/next/no-page-custom-font": "off"
}
}
ä¿®æ¹æç¦ç¨åæ¯æçæä»¶ï¼reactãreact-hooksãnextï¼æä¾çä»»ä½è§å
{
"extends": "next/core-web-vitals"
}
npm install -S eslint-config-prettier
# or
yarn add --dev eslint-config-prettier
{
"extends": ["next", "prettier"]
}
const path = require('path')
const buildEslintCommand = (filenames) =>
`next lint --fix --file ${filenames
.map((f) => path.relative(process.cwd(), f))
.join(' --file ')}`;
module.exports = {
'*.{js,jsx,ts,tsx}': [buildEslintCommand],
}
å
容添å å°é¡¹ç®æ ¹ç®å½ä¸ç .lintstagedrc.js æä»¶ä¸ï¼ä»¥æå® --file æ å¿
npx create-next-app@latest --ts
# or
yarn create next-app --typescript
# or
pnpm create next-app --ts
import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next'
export const getStaticProps: GetStaticProps = async (context) => {
// ...
}
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
export const getServerSideProps: GetServerSideProps = async (context) => {
// ...
}
touch tsconfig.json
æ¨è¿å¯ä»¥éè¿å¨ next.config.js æä»¶ä¸è®¾ç½® typescript.tsconfigPath 屿§æ¥æä¾ tsconfig.json æä»¶çç¸å¯¹è·¯å¾
import type {
NextApiRequest, NextApiResponse
} from 'next'
export default (
req: NextApiRequest,
res: NextApiResponse
) => {
res.status(200).json({ name:'John Doe' })
}
æ¨è¿å¯ä»¥é®å ¥ååºæ°æ®ï¼
import type {
NextApiRequest, NextApiResponse
} from 'next'
type Data = {
name: string
}
export default (
req: NextApiRequest,
res: NextApiResponse<Data>
) => {
res.status(200).json({ name:'John Doe' })
}
使ç¨å
置类å AppProps å¹¶å°æä»¶åæ´æ¹ä¸º ./pages/_app.tsxï¼å¦ä¸æç¤ºï¼
import type { AppProps } from 'next/app'
export default function MyApp({
Component, pageProps
}: AppProps) {
return <Component {...pageProps} />
}
// @ts-check
/**
* @type {import('next').NextConfig}
**/
const nextConfig = {
/* é
ç½®é项å¨è¿é */
}
module.exports = nextConfig
module.exports = {
typescript: {
ignoreBuildErrors: true,
},
}
å±é©å°å 许ç产æå»ºæå宿ï¼å³ä½¿æ¨çé¡¹ç®æç±»åé误
å°ç¯å¢åéä» .env.local å è½½å° process.env ä¸
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
使ç¨ç¯å¢åé
// 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,
})
// ...
}
# .env
HOSTNAME=localhost
PORT=8080
HOST=http://$HOSTNAME:$PORT
妿æ¨å°è¯ä½¿ç¨å®é
å¼ä¸å¸¦æ $ çåéï¼åéè¦åè¿æ ·å¯¹å
¶è¿è¡è½¬ä¹ï¼\$
# .env
A=abc
# becomes "preabc"
WRONG=pre$A
# becomes "pre$A"
CORRECT=pre\$A
为äºåæµè§å¨å
¬å¼åéï¼æ¨å¿
é¡»å¨åéåå ä¸ NEXT_PUBLIC_ åç¼
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
NEXT_PUBLIC_ANALYTICS_ID å¯ä»¥å¨æ¤å¤ä½¿ç¨ï¼å 为å®çåç¼æ¯ NEXT_PUBLIC_
// pages/index.js
import setupAnalyticsService from '../lib/my-analytics-service'
//
// å®å°å¨æå»ºæ¶è½¬æ¢ä¸º `setupAnalyticsService('abcdefghijk')`
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() {
return <h1>Hello World</h1>
}
export default HomePage
è·¯ç±å¨å°èªå¨å°å为 index çæä»¶è·¯ç±å°ç®å½çæ ¹ç®å½
| :-- | -- |
|---|---|
pages/index.js | / |
pages/blog/index.js | /blog |
è·¯ç±å¨æ¯æåµå¥æä»¶ã妿å建åµå¥æä»¶å¤¹ç»æï¼æä»¶å°ä»¥åæ ·çæ¹å¼èªå¨è·¯ç±
| :-- | -- |
|---|---|
pages/blog/first-post.js | /blog/first-post |
pages/dashboard/settings/username.js | /dashboard/settings/username |
å¨æè·¯ç±
| :-- | -- |
|---|---|
pages/blog/[slug].js | /blog/:slug/blog/hello-world |
pages/[username]/settings.js | /:username/settings/foo/settings |
pages/post/[...all].js | /post/*/post/2020/id/title |
妿æ¨å建ä¸ä¸ªå为 pages/posts/[pid].js çæä»¶ï¼é£ä¹å®å¯ä»¥å¨ posts/1ãposts/2 çå¤è®¿é®
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
ä½¿ç¨ useRouter è·åå¨æè·¯ç±åæ° pid
import Link from 'next/link'
export default function Home() {
return (
<ul>
<li>
<Link href="/">é¦é¡µ</Link>
</li>
<li>
<Link href="/about">å
³äºæä»¬</Link>
</li>
<li>
<Link href="/blog/hello-world">
åæ
</Link>
</li>
</ul>
)
}
| :-- | -- |
|---|---|
/ | pages/index.js |
/about | pages/about.js |
/blog/hello-world | pages/blog/[slug].js |
import Link from 'next/link'
export default function Posts({ posts }) {
return (
<Link href={`/blog/${encodeURIComponent(post.slug)}`}>
æ é¢
</Link>
)
}
import Link from 'next/link'
export default function Posts({ posts }) {
return (
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: posts.slug },
}}
>
æ é¢
</Link>
)
}
èè以ä¸é¡µé¢ 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
å°å¨æè·¯ç±ç客æ·ç«¯å¯¼èªç± next/link å¤ç
import Link from 'next/link'
export default function Home() {
return (
<div>
<Link href="/post/abc">
è½¬å° pages/post/[pid].js
</Link>
<Link href="/post/abc?foo=bar">
ä¹è½¬å° pages/post/[pid].js
</Link>
<Link href="/post/abc/a-comment">
è½¬å° pages/post/[pid]/[comment].js
</Link>
</div>
)
}
工使¹å¼ç¸åã é¡µé¢ pages/post/[pid]/[comment].js å°å¹é
è·¯ç± /post/abc/a-comment å¹¶ä¸å®çæ¥è¯¢å¯¹è±¡å°æ¯ï¼
{ "pid": "abc", "comment": "a-comment" }
å¯ä»¥éè¿å¨æ¬å·å
æ·»å ä¸ä¸ªç¹ (...) æ¥æ©å±å¨æè·¯ç±ä»¥æè·ææè·¯å¾ï¼pages/post/[...slug].js å¹é
/post/aï¼ä¹å¹é
/post/a/bã/post/a/b/c ç
// /post/a
{ "slug": ["a"] }
// /post/a/b
{ "slug": ["a", "b"] }
ä½¿ç¨ [[...slug]]ï¼pages/post/[[...slug]].js å°å¹é
/postã/post/aã/post/a/b ç
// GET `/post` (empty object)
{ }
// `GET /post/a` (single-element array)
{ "slug": ["a"] }
// `GET /post/a/b` (multi-element array)
{ "slug": ["a", "b"] }
import { useRouter } from 'next/router'
export default function ReadMore() {
const router = useRouter()
return (
<button
onClick={() => router.push('/about')}
>
ç¹å»è¿éé
读æ´å¤
</button>
)
}
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// å½åç½å为â/â
export default function Page() {
const router = useRouter()
useEffect(() => {
// å§ç»å¨ç¬¬ä¸æ¬¡æ¸²æåè¿è¡å¯¼èª
router.push('/?counter=10', undefined, { shallow: true })
}, [])
useEffect(() => {
// counter åäºï¼
}, [router.query.counter])
}
æµ
è·¯ç±ä»
éç¨äºå½å页é¢ä¸ç URL æ´æ¹ã ä¾å¦ï¼å设æä»¬æå¦ä¸ä¸ªå为 pages/about.js ç页é¢ï¼å¹¶ä¸æ¨è¿è¡ä»¥ä¸å½ä»¤ï¼
router.push('/?counter=10', '/about?counter=10', { shallow: true })
ç±äºè¿æ¯ä¸ä¸ªæ°é¡µé¢ï¼å®ä¼å¸è½½å½å页é¢ï¼å è½½æ°é¡µé¢å¹¶çå¾ æ°æ®è·åï¼å³ä½¿æä»¬è¦æ±è¿è¡æµ å±è·¯ç±