# next-tailwind **Repository Path**: pardon110/next-tailwind ## Basic Information - **Project Name**: next-tailwind - **Description**: 技术验证,笔记同步 nextjs + Tailwind - **Primary Language**: NodeJS - **License**: EPL-1.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-05-28 - **Last Updated**: 2025-04-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: nextjs, Tailwind, React ## README # next-tailwind `react` 项目,`nextjs` 采用 `page router` 及 第三代 `css` 框架 `Tailwind` ## nextjs ### Pages and Layouts - `pages/_app.js` - `_app` 容器组件 `{ Component, pageProps }` ``` import Layout from '../components/layout'; export default function MyApp({ Component, pageProps }) { return ( ); } ``` ### Dynamic Routes - 动态路由默认使用方括号包裹,`Dynamic Segments can be access from useRouter`. - `[folderName]` - `[...folderName]` Catch-all Segments - `[[...folderName]]` 可选 - 应用范围: 目录和文件名都适用 | Route | Example URL | params | | ----------------------------- | --------------- | ------------------------- | | pages/posts/[slug].js | /posts/a | { slug: 'a' } | | pages/optional/[...slug].js | /optional/a/b/c | { slug: ['a', 'b', 'c'] } | | pages/optional/[[...slug]].js | /optional | {} | | pages/optional/[[...slug]].js | /optional/a/b/c | { slug: ['a', 'b', 'c'] } | ### project structure - Route Groups | (folder) | Group routes without affecting routing | | -------- | ------------------------------------------------ | | _folder | Opt folder and all child segments out of routing | - Parallel and Intercepted Routes | @folder | Named slot | | -------------- | -------------------------- | | (.)folder | Intercept same level | | (..)folder | Intercept one level above | | (..)(..)folder | Intercept two levels above | | (...)folder | Intercept from root | ### `head` 组件渲染流程 下面是 Next.js 在处理 `Head` 组件时的流程: 1. 在服务端渲染阶段,Next.js 会在渲染每个页面时初始化一个全局的 `HeadManager` 对象。 2. 在页面组件中,当使用 `Head` 组件时,会将其包含的元素添加到 `HeadManager` 对象中。 3. 在服务端渲染完成后,Next.js 会将渲染完成的 HTML 携带着 `HeadManager` 对象发送给客户端。 4. 当客户端加载新页面并纯客户端渲染时,Next.js 会重新初始化一个新的 `HeadManager` 对象。 5. 新页面的 HTML 中包含一个特殊标记,指示 Next.js 将 `HeadManager` 对象中的元素更新到 `` 中。 6. 当客户端浏览器解析 HTML 时,它会在发现特殊标记时,触发 `HeadManager` 对象的 `updateHead` 方法,使其将 `Head` 组件中的新元素添加到页面的 `` 部分中。 7. 每当需要更新页面的 `` 部分中的元素时(例如切换到新页面),`HeadManager` 对象的 `updateHead` 方法都会被触发,以实时更新视图。 ``` +-------------+ +----------------------+ | Next.js | | Browser | | Server | | | +------+------| | | | | | | | | | | | | | | | | 1. Generate HTML with | | |<---------------------------------------------+ | | HeadManager object on each page | | | | | | | | 2. Send HTML with HeadManager object | | to client | +---------------------------------------------->+ | | | | | | 4. Initialize new HeadManager | |<----------------------------------------------+ | | object on client 3. Render | | component| | 5. Update using new with | |<---------------- HeadManager object Head | | component| | 6. Perform client-side routing | |<---------------- to new page | | | | 7. Repeat steps 3-6 for each page change | | ``` - 在 Next.js 中,Head 组件可以出现在任意非 head 标签之内,只需确保它位于组件返回的 JSX 根元素的直接内部即可 ### Environment Variable Load Order 1. `process.env` 2. `.env.$(NODE_ENV).local` 3. `.env.local (Not checked when NODE_ENV is test.)` 4. `.env.$(NODE_ENV)` 5. `.env` ### hooks/properties for nextjs - 在 `Next.js` 中,每个页面都是一个 React 组件,可以通过导出默认属性来定义页面的行为和内容 - `getServerSideProps`:用于在每次请求时获取页面的数据,返回一个对象,包含页面的 props。这个方法在每次请求时都会被调用,可以用于动态生成页面内容。 - `getInitialProps`:用于在每次请求时获取页面的数据,返回一个对象,包含页面的 props。这个方法已经被废弃,推荐使用 `getServerSideProps` 或 `getStaticProps`。 - `pageProps`:包含页面的 props,可以在组件中直接使用。 - `Component`:页面的 React 组件,可以在组件中直接使用。 - `getStaticPaths (Static Generation):` 为动态路由参数构建所需的路径 - `getStaticProps:(Static Generation)`: 负责根据指定的动态路由参数获取数据 ### Shallow Routing - 它只匹配 URL 的第一层路径,而不考虑其后续路径。 - 意味着在 Shallow Routing 中,所有具有相同路径的 URL 都将被映射到同一个页面或组件上。 - 应用场景 路由策略通常用于简单的页面或组件,例如主页、登录页等 ``` router.push('/?counter=10', '/about?counter=10', { shallow: true }) ``` ### API Routes - Every API Route can export a config object to change the default configuration ``` export const config = { api: { bodyParser: { sizeLimit: '1mb', }, }, }; ``` ### Authenticating - pattern - `data-fetching strategy` - `static generation` - `server-side` - 身份验证模块 - with-iron-session - next-auth-example - with-passport - with-passport-and-next-connect - auth0 ### `middleware.js` - matching paths order - headers from next.config.js - redirects from next.config.js - Middleware (rewrites, redirects, etc.) - beforeFiles (rewrites) from next.config.js - Filesystem routes (public/, _next/static/, pages/, app/, etc.) - afterFiles (rewrites) from next.config.js - Dynamic Routes (/blog/[slug]) - fallback (rewrites) from next.config. - `server code` ``` // middleware.js export default function myServerMiddleware(req, res, next) { console.log('This is my server middleware'); next(); } ``` ``` // pages/api/mypage.js import myServerMiddleware from '../../middleware'; // 页面组件或 API 路由处理函数 function handler(req, res) { return res.status(200).json({ text: 'Hello' }); } // 使用服务端中间件 export default myServerMiddleware(handler); ``` - `client code` ``` // middleware.js function myClientMiddleware(request, response, next) { console.log('This is my client middleware'); // 在请求头中添加自定义信息 request.headers["x-my-header"] = "hello"; next(); } export default myClientMiddleware; ``` - `useEffect` ``` // pages/index.js import myClientMiddleware from '../middleware'; function IndexPage() { useEffect(() => { myClientMiddleware(); }, []); // 在组件加载时执行中间件函数 return
Hello world
; } export default IndexPage; ``` - 路由配置 ``` // pages/middleware.js const legacyPrefixes = ['/docs', '/blog']; export default async function middleware(req) { const { pathname } = req.nextUrl; if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) { return NextResponse.next(); } // apply trailing slash handling if ( !pathname.endsWith('/') && !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/) ) { req.nextUrl.pathname += '/'; return NextResponse.redirect(req.nextUrl); } } ``` - 通过配置实现 ``` // next.config.js module.exports = { async rewrites() { return [ { source: '/api/:path*', destination: 'http://localhost:3000/api/:path*', }, ] }, async redirects() { return [ { source: '/old-path', destination: '/new-path', permanent: true, }, ] }, } ``` ### rendering - 同构应用 > 同构模块的出现是为了解决单页应用(SPA)在网络环境差、首次加载时间长、SEO 不友好等方面的问题。 > 在单页应用中,大部分 HTML、CSS、JS 等资源只在首次加载时请求一次,以后的操作只会请求 JSON 数据,通过 AJAX 异步刷新页面。这种应用方式的好处在于用户操作更顺畅,但缺 点也很明显:首次加载速度慢、SEO 不友好。 > 单页应用首次加载慢的原因是因为在请求 HTML 文件之外,还需要加载大量的 JavaScript 文件来构建页面,这些文件在首次加载时会花费很长时间。而且搜索引擎的爬虫只会对页面进行解析并拿取其中的文本信息,对于单页应用中使用 ajax 获取内容生成的数据会不良影响 SEO。 > 为了解决以上问题,同构模块就出现了。同构应用是指在服务端运行一段 JS 代码,生成 HTML 文件后再返回给客户端。也就是说,同样一段代码,在服务器端和客户端都会执行,处理逻辑相同,生成的结果也完全一致。因此同构应用能够在第一次请求时快速返回完整的 HTML 页面,提高页面加载速度,而且因为服务端构建页面返回给客户端所得到的 HTML 已经包含所有的内容,包括从服务端读取的数据,因此对搜索引擎的爬取也是十分友好的。 #### `nextjs` 前后端同构模块, `build --> hydration` 两个主阶段渲染 > 前后端同构基本原理 > > 将一部分代码在服务器端运行,将另一部分代码在浏览器端运行 > > 并将它们的状态和数据同步. > > ReactDOM.hydrate()方法将服务器端生成的`HTML`(含注入的服务端数据`__NEXT_DATA__`)在客户端 > - `ReactDomServer.renderToString()`: 服务端渲染 - `React.hydrate()` 客户端渲染 水化? - `ReactDOM.render()` 基本渲染 - `ReactDom.createPortal()` 脱离父组件渲染,如弹窗... ### tree - 虚拟 `DOM` - `Fiber` 支持可中断渲染 16+ - `browser` 相关树 `Dom` 树, `css rule` 规则树, `render` 渲染树 ### React Hook - `useCallback` 将函数的引用进行缓存,避免在每次组件重新渲染时都要重新创建函数 - `useRef` 在函数式组件中引用 DOM 元素或任何值。它类似于使用 ref 属性的类组件 - `useState` 在函数式组件中添加状态。它返回一个包含状态值和一个更新状态的函数的数组。 - `useReducer` 接受一个 `reducer` 函数和初始状态,返回一个包含当前复杂状态和 `dispatch` 函数的数组 - `useEffect` 在函数式组件中定义副作用 - `useContext` 在函数式组件中使用 `React` 上下文 - `useMemo` 在函数式组件中缓存计算结果,只有当依赖项更改时才会重新计算 ### About app router - A page is UI that's unique to a route ``` // app/feed/[item]/page.js export default function Page({ params, searchParams }) { return

My Page

; } ``` - params (optional) | 路径格式 | 请求路径 | 参数对象 | | ------------------------------------ | -------- | --------------------------------------- | | `app/shop/[slug]/page.js` | `/shop/1` | `{ slug: '1' }` | | `app/shop/[category]/[item]/page.js` | `/shop/1/2` | `{ category: '1', item: '2' }` | | `app/shop/[...slug]/page.js` | `/shop/1/2` | `{ slug: ['1', '2'] }` | - searchParams (optional) | URL | searchParams | | -------------- | ------------------- | | `/shop?a=1` | `{ a: '1' }` | | `/shop?a=1&b=2` | `{ a: '1', b: '2' }` | | `/shop?a=1&a=2` | `{ a: ['1', '2'] }` | ### React ### Route Handlers vs Api routes - They do not participate in layouts or client-side navigations like page. - There cannot be a route.js file at the same route as page.js. - **Each route.js or page.js file takes over all HTTP verbs for that route.** | Page | Route | Result | | -------------------|---------------------|----------| | app/page.js | app/route.js | Conflict | | app/page.js | app/api/route.js | Valid | | app/[user]/page.js | app/api/route.js | Valid | - vs - Route Handlers - 通过匹配 Pages 目录下的文件名(文件名即路由)来映射到不同的页面 - 通过导出一个 React 组件来描述页面内容和行为 - API routes - 通过定义 Pages/api 目录下的特殊文件来创建 - 接收和处理不同的 HTTP 请求,并返回 JSON 格式的数 - 不需要渲染组件,而只需要处理数据