# 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 格式的数
- 不需要渲染组件,而只需要处理数据