发布于2024年02月01日浏览量112
文件 | 描述 |
---|---|
layout | 布局 |
page | 页 |
loading | 加载页面 |
not-found | 未找到页面 |
error | 错误页面 |
global-error | 全局错误页面 |
template | 重新渲染页面 |
default | 并行路由回退页面 |
基于nextjs官方教程
/app/lib
存放可以复用的函数或者数据结构
/app/ui
存放ui组件
/scripts
存放脚本
className={xxx.yy}
可应用css文件中的某个特定样式
其中clsx是一个便于切换类名的库,使用方法例如className={clsx( 'inline-flex items-center rounded-full px-2 py-1 text-xs', { 'bg-gray-100 text-gray-500': status === 'pending', 'bg-green-500 text-white': status === 'paid', }, )}
Next.js 会在构建时下载字体文件,并将它们与其他静态资产一起托管,这样当用户访问应用程序时就不会有额外的字体网络请求 例如:
export const inter = Inter({ subsets: ['latin'] });
export const harmony = localFont({ src: '../../public/fonts/SmileySans.ttf.woff2' });
<body className={`${inter.className} antialiased`}>{children}</body>
(antialiased为可选的,字体边缘抗锯齿)import Image from 'next/image';
以使用nextjs优化图像,但需要注意
<Image src="/hero-desktop.png" width={1000} height={760} className="hidden md:block" alt="Screenshots of the dashboard project showing desktop version" />
其中width和height定义了图像的纵横比,在加载时能防止图像偏移,并且比例在不同屏幕尺寸下仍能保持一致
但是对于字体和图像的优化远不止这些,还需多加了解!!
Next使用嵌套式文件夹进行路由匹配,文件夹名为路由,其中的page.tsx和layout.tsx分别对应自身的页面和布局 使用布局的一个好处是,在导航时,只有页面组件会更新,而布局不会重新呈现,称为部分渲染
在Next应用中使用<a>进行导航时,会触发整个页面的重新获取,十分浪费资源
使用import Link from 'next/link';
以进行客户端导航
与React的SPA不同,Next会自动切分route,并在第一次加载时加载整个应用的完整代码
所以在这个情况下,每个页面都是独立的,即使有个页面抛出error也不会影响其他页面的运行
Next会自动prefetch the code for the linked route in the background,以优化点击链接后的加载速度
可用于导航栏
'use client'; import { usePathname } from 'next/navigation'; import clsx from 'clsx'; function() { const pathname = usePathname(); className={clsx({ 'bg-sky-100 text-blue-600': pathname === link.href})} }
总体来说,在page统一获取数据,可以使用const data = await Promise.all([ invoiceCountPromise, customerCountPromise, invoiceStatusPromise, ]);
的方式同时发出多个请求
使用静态呈现时,数据提取和呈现在生成时(部署时)或重新验证期间在服务器上进行 每当用户访问应用程序时都会先提供缓存的结果
使用动态呈现时,在请求时(当用户访问页面时)在服务器上为每个用户呈现内容 动态渲染有几个好处:
但是最终页面的加载速度取决于最慢的数据获取请求
流式处理是一种数据传输技术,它允许将路由分解为更小的“块”,并在它们准备就绪时逐步将它们从服务器流式传输到客户端 在Next中实现流式处理有两种方法:
loading.tsx
<Suspense>
与page.tsx, layout.tsx处于同级目录
loading.tsx
是一个特殊的Next.js文件,它建立在Suspense之上,它允许您创建回退UI,以便在页面内容加载时显示为替换<Suspense fallback={<RevenueChartSkeleton />}> <RevenueChart /> </Suspense>
用于实现搜索功能的 Next.js 客户端钩子:
useSearchParams
允许访问当前URL的参数。例如,此/dashboard/invoices?page=1&query=pending
的搜索参数为{page: '1', query: 'pending'}
usePathname
允许读取当前URL的路径名。例如,对于路由/dashboard/invoices``usePathname
,将返回'/dashboard/invoices'
useRouter
以编程方式在客户端组件内的路由之间启用导航搜索框组件使用'use client'
以确认为客户端组件,此时才可使用事件侦听等钩子
因为是客户端组件,此时console.log的位置为浏览器控制台中;否则在代码终端处
import { useSearchParams } from 'next/navigation'; export default function Search() { const searchParams = useSearchParams(); const pathname = usePathname(); const { replace } = useRouter(); function handleSearch(term: string) { const params = new URLSearchParams(searchParams); if (term) { params.set('query', term); } else { params.delete('query'); } replace(`${pathname}?${params.toString()}`); } // ... }
在输入框加入defaultValue={searchParams.get('query')?.toString()}
export default async function Page({ searchParams,}: { searchParams?: { query?: string; page?: string; }; }) { const query = searchParams?.query || ''; const currentPage = Number(searchParams?.page) || 1; // ... return ( <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />} > <Table query={query} currentPage={currentPage} /> </Suspense> )
记得debounce
在分页组件中获取传入组件的总页数,并根据当前params中的数据确定页码
const pathname = usePathname(); const searchParams = useSearchParams(); const currentPage = Number(searchParams.get('page')) || 1;
创建函数,在切换页码时触发
const createPageURL = (pageNumber: number | string) => { const params = new URLSearchParams(searchParams); params.set('page', pageNumber.toString()); return `${pathname}?${params.toString()}`; };
React服务器操作允许直接在服务器上运行异步代码。它们消除了创建API端点来改变数据的需要。相反,可以直接编写在服务器上执行的异步函数,并从客户端或服务器组件调用这些函数
// Server Component export default function Page() { // Action async function create(formData: FormData) { 'use server'; // Logic to mutate data... } // Invoke the action using the "action" attribute return <form action={create}>...</form>; }
好处:渐进式增强 - 即使在客户端上禁用了js,表单也能正常工作
新建actions.ts
用于导出所有服务器函数,在头部添加'use client';
创建发票路由/invoices/create/page.tsx
'use client'; import { z } from 'zod'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; const FormSchema = z.object({ id: z.string(), customerId: z.string(), amount: z.coerce.number(), status: z.enum(['pending', 'paid']), date: z.string(), }); const CreateInvoice = FormSchema.omit({ id: true, date: true }); export async function createInvoice(formData: FormData) { const { customerId, amount, status } = CreateInvoice.parse({ customerId: formData.get('customerId'), amount: formData.get('amount'), status: formData.get('status'), }); await sql; // 插入数据库操作 revalidatePath('/dashboard/invoices'); redirect('/dashboard/invoices'); }
最后在表单中调用<form action={createInvoice}>
注意:
由于更新的发票id是变化的,所以此处使用方括号包裹文件夹来创建动态路由/invoices/[id]/edit/page.tsx
import Form from '@/app/ui/invoices/edit-form'; import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data'; export default async function Page({ params }: { params: { id: string } }) { const id = params.id; const [invoice, customers] = await Promise.all([ fetchInvoiceById(id), fetchCustomers(), ]); // ... }
但为了在编辑表单中使用更新数据的函数,不能直接像之前一样使用<form action={updateInvoice(id)}>
,而应该使用
import { updateInvoice } from '@/app/lib/actions'; export default function EditInvoiceForm({ invoice, customers,}: { invoice: InvoiceForm; customers: CustomerField[]; }) { const updateInvoiceWithId = updateInvoice.bind(null, invoice.id); return ( <form action={updateInvoiceWithId}> <input type="hidden" name="id" value={invoice.id} /> </form> ); }
直接将删除按钮包裹在表单中,如下
import { deleteInvoice } from '@/app/lib/actions'; // ... export function DeleteInvoice({ id }: { id: string }) { const deleteInvoiceWithId = deleteInvoice.bind(null, id); return ( <form action={deleteInvoiceWithId}> <button className="rounded-md border p-2 hover:bg-gray-100"> <span className="sr-only">Delete</span> <TrashIcon className="w-4" /> </button> </form> ); }
!! 记得使用try-catch包裹服务器操作
可以创建文件error.tsx
作为同级位置路由出现错误时的UI
示例:
'use client'; import { useEffect } from 'react'; export default function Error({ error, reset,}: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { // Optionally log the error to an error reporting service console.error(error); }, [error]); return ( <main className="flex h-full flex-col items-center justify-center"> <h2 className="text-center">Something went wrong!</h2> <button onClick={ // Attempt to recover by trying to re-render the invoices route () => reset() } > Try again </button> </main> ); }
'use client';
可以创建文件not-found.tsx
,在代码中调用notFound()
方法即可进入
在package.json
的scripts
下添加"lint": "next lint"
,然后执行npm run lint
因框架不同,具体实现详见教程
具体实现结合自身需求,主要思路为使用中间件middleware.ts
拦截路由
标题元数据:负责浏览器选项卡上显示的网页标题
<title>Page Title</title>
描述元数据:提供网页内容的简要概述,通常显示在搜索引擎结果中
<meta name="description" content="A brief description of the page content." />
关键字元数据:包括与网页内容相关的关键字,帮助搜索引擎索引页面
<meta name="keywords" content="keyword1, keyword2, keyword3" />
Open Graph 元数据:增强了网页在社交媒体平台上共享时的表示方式,提供标题、描述和预览图像等信息
<meta property="og:title" content="Title Here" />
<meta property="og:description" content="Description Here" />
<meta property="og:image" content="image_url_here" />
网站图标元数据:将网站图标连接到网页
<link rel="icon" href="path/to/favicon.ico" />
next有一个元数据API,可用于定义应用程序元数据。有两种方法可以向应用程序添加元数据:
layout.js
或page.js
文件中的静态元数据对象,或使用动态generateMetadata
函数favicon.ico
、apple-icon.jpg
和icon.jpg
:用于网站图标和图标opengraph-image.jpg
以及twitter-image.jpg
:用于社交媒体图像robots.txt
:提供有关搜索引擎抓取的说明sitemap.xml
:提供有关网站结构的信息在/public
文件夹中有两个图像:favicon.ico
和opengraph-image.jpg
将这些图像移动到/app
文件夹的根目录,next将自动识别这些文件并将其用作图标和OG图像
可以包含来自任何OR文件的元数据对象,以添加其他页面信息如标题和描述。layout.js
中的任何元数据都将由使用它的所有页面继承
import { Metadata } from 'next'; export const metadata: Metadata = { title: 'Acme Dashboard', description: 'The official Next.js Course Dashboard, built with App Router.', metadataBase: new URL('https://next-learn-dashboard.vercel.sh'), };
每个页面都可以创建属于自己的元数据,同时也可以使用模板:
import { Metadata } from 'next'; export const metadata: Metadata = { title: { template: '%s | Acme Dashboard', default: 'Acme Dashboard', }, description: 'The official Next.js Learn Dashboard built with App Router.', metadataBase: new URL('https://next-learn-dashboard.vercel.sh'), };
此时在invoices页面中添加元数据export const metadata: Metadata = { title: 'Invoices',};
,即可看到页面标题为Invoices | Acme Dashboard
My Nocturzone
LEON の 熬夜空间
1 年 5 月 1 天 19 小时 59 分钟
My Nocturzone
LEON の 熬夜空间