Skip to content

Commit e2f401f

Browse files
committed
2 parents f9ab3d5 + 07d4ad1 commit e2f401f

10 files changed

Lines changed: 151 additions & 92 deletions

File tree

.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default defineConfig({
7070
{ text: 'Vercel 静态导出', link: '/user-guide/deploy/vercel-static' },
7171
{ text: 'Cloudflare 文档站', link: '/user-guide/deploy/cloudflare-pages-docs' },
7272
{ text: 'Cloudflare 博客静态', link: '/user-guide/deploy/cloudflare-pages' },
73+
{ text: 'EdgeOne Pages', link: '/user-guide/deploy/edgeone-pages' },
7374
{ text: '构建性能调优', link: '/user-guide/deploy/build-tuning' },
7475
{ text: 'Netlify', link: '/user-guide/deploy/netlify' },
7576
{ text: 'VPS', link: '/user-guide/deploy/vps' }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# 腾讯云 EdgeOne Pages 部署
2+
3+
> 完整排错见仓库 [DEPLOYMENT.md · EdgeOne Pages](https://github.com/notionnext-org/NotionNext/blob/main/DEPLOYMENT.md#腾讯云-edgeone-pages)
4+
5+
## 环境要求
6+
7+
|| 说明 |
8+
| --- | --- |
9+
| Node | 与仓库根目录 **`.nvmrc` 完全一致**(当前为 `20.18.0`|
10+
| 包管理 | `yarn install``yarn build``yarn export` |
11+
| 必配变量 | `NOTION_PAGE_ID` 等(见 [配置站点](../config-site.md)|
12+
13+
EdgeOne 会按 `.nvmrc` 切换 Node。若日志出现 **`Failed to switch to Node.js 20.20.0`** 等,说明仓库过旧或控制台所选版本与 `.nvmrc` 不一致——请拉取最新 `main`,并在 **项目设置 → Node.js 版本** 中选择与 `.nvmrc` 相同的补丁号(如 `20.18.0`)。
14+
15+
## 推荐:框架预设「Next SSG」
16+
17+
部分用户在默认 Pages 构建流程下,Node 切换成功后会在 `yarn install` 阶段遇到 **`ENOSPC: no space left on device`**(构建机 `/dev/shm` 或磁盘不足)。
18+
19+
若出现该情况,可在 EdgeOne 控制台尝试将**框架预设**改为 **Next SSG**(或平台提供的 Next 静态导出类预设)后重新部署。该方式通常由平台优化安装与构建路径,占用更小,社区反馈可成功完成部署。
20+
21+
仍失败时请结合下方 ENOSPC 小节与 [构建性能调优](./build-tuning.md)
22+
23+
## `yarn install` 报 ENOSPC
24+
25+
日志中若已有 **`Now, we're on node version v20.18.0`**,则 Node 版本已正常,失败原因是**构建机空间不足**,与 NotionNext 代码无关。
26+
27+
可尝试(按顺序):
28+
29+
1. 改用上文 **Next SSG** 预设,或关闭「依赖/构建缓存」后无缓存重构建一次
30+
2. 向 EdgeOne / 腾讯云工单说明 `yarn install` 阶段 ENOSPC,询问构建盘配额
31+
3. 若平台支持,设置 `YARN_CACHE_FOLDER` 到空间更大的路径(以官方文档为准)
32+
4. 在 GitHub Actions 等环境完成 `yarn build` / `yarn export`,仅将静态产物同步到 EdgeOne 托管
33+
34+
## 构建超时
35+
36+
Notion 拉取慢导致单页超时,请参阅 **[构建性能调优](./build-tuning.md)**,例如:
37+
38+
```env
39+
BUILD_PREFETCH_ENABLED=false
40+
STATIC_PAGE_GENERATION_TIMEOUT=600
41+
```
42+
43+
## 相关 Issue
44+
45+
- [#4014](https://github.com/notionnext-org/NotionNext/issues/4014) — EdgeOne Node 版本与 ENOSPC 排错(已在 `main``.nvmrc` 对齐 `20.18.0`

docs/user-guide/deploy/index.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
| [vercel-redeploy.md](./vercel-redeploy.md) | 重新部署 |
2020
| [netlify.md](./netlify.md) | Netlify(4.0.9+) |
2121
| [cloudflare-pages.md](./cloudflare-pages.md) | Cloudflare 静态 |
22+
| [edgeone-pages.md](./edgeone-pages.md) | **腾讯云 EdgeOne**(Node 版本、ENOSPC、Next SSG 预设) |
2223
| [build-tuning.md](./build-tuning.md) | **构建超时 / Notion 预热与限流**(环境变量) |
2324
| [vps.md](./vps.md) | VPS / Docker(Node 20+) |
2425

@@ -32,6 +33,6 @@
3233
- **Yarn**`yarn``yarn build` / `yarn export`
3334
- 必配:`NOTION_PAGE_ID`
3435

35-
## EdgeOne / 构建缓存
36+
## EdgeOne
3637

37-
构建时若 `.next/cache` 不可写,项目会回退系统临时目录(Netlify 等)。见上游 PR #4046 `lib/cache/build_session.js`
38+
Node 版本、磁盘 `ENOSPC`、框架预设 **Next SSG** 等见 **[edgeone-pages.md](./edgeone-pages.md)**构建时若 `.next/cache` 不可写,项目会回退系统临时目录,见 `lib/cache/build_session.js`

lib/global.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@/themes/theme'
88
import { useUser } from '@clerk/nextjs'
99
import { useRouter } from 'next/router'
10-
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
10+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
1111
import { generateLocaleDict, initLocale, redirectUserLang } from './utils/lang'
1212

1313
/**
@@ -52,7 +52,7 @@ export function GlobalContextProvider(props) {
5252
const fullWidth = post?.fullWidth ?? false
5353

5454
// 切换主题
55-
function switchTheme() {
55+
const switchTheme = useCallback(() => {
5656
const query = router.query
5757
const currentTheme = query.theme || theme
5858
const currentIndex = THEMES.indexOf(currentTheme)
@@ -61,30 +61,30 @@ export function GlobalContextProvider(props) {
6161
query.theme = newTheme
6262
router.push({ pathname: router.pathname, query })
6363
return newTheme
64-
}
64+
}, [router, theme, THEMES])
6565

6666
// 抓取主题配置
67-
const updateThemeConfig = async theme => {
67+
const updateThemeConfig = useCallback(async theme => {
6868
const config = await getThemeConfig(theme)
6969
SET_THEME_CONFIG(config)
70-
}
70+
}, [])
7171

7272
// 切换深色模式
73-
const toggleDarkMode = () => {
73+
const toggleDarkMode = useCallback(() => {
7474
const newStatus = !isDarkMode
7575
saveDarkModeToLocalStorage(newStatus)
7676
updateDarkMode(newStatus)
7777
const htmlElement = document.getElementsByTagName('html')[0]
7878
htmlElement.classList?.remove(newStatus ? 'light' : 'dark')
7979
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
80-
}
80+
}, [isDarkMode])
8181

82-
function changeLang(lang) {
82+
const changeLang = useCallback(lang => {
8383
if (lang) {
8484
updateLang(lang)
8585
updateLocale(generateLocaleDict(lang))
8686
}
87-
}
87+
}, [])
8888

8989
// 添加路由变化时的语言处理
9090
useEffect(() => {
@@ -133,7 +133,7 @@ export function GlobalContextProvider(props) {
133133
}
134134
}, [router.events])
135135

136-
const contextValue = {
136+
const contextValue = useMemo(() => ({
137137
isLiteMode,
138138
isLoaded,
139139
isSignedIn,
@@ -156,7 +156,7 @@ export function GlobalContextProvider(props) {
156156
siteInfo,
157157
categoryOptions,
158158
tagOptions
159-
}
159+
}), [isLiteMode, isLoaded, isSignedIn, user, fullWidth, NOTION_CONFIG, THEME_CONFIG, toggleDarkMode, onLoading, setOnLoading, lang, changeLang, locale, updateLocale, isDarkMode, updateDarkMode, theme, setTheme, switchTheme, siteInfo, categoryOptions, tagOptions])
160160
globalSnapshot = contextValue
161161

162162
return (

lib/utils/errorHandler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react'
12
/**
23
* 统一错误处理工具类
34
*/

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@
7070
"@clerk/nextjs": "^5.7.6",
7171
"@headlessui/react": "^1.7.19",
7272
"@next/bundle-analyzer": "^12.3.7",
73-
"@supabase/supabase-js": "^2.57.4",
73+
"@supabase/supabase-js": "^2.106.0",
7474
"@tabler/icons-react": "^3.36.1",
7575
"@vercel/analytics": "^1.6.1",
76-
"@vercel/functions": "^3.4.3",
76+
"@vercel/functions": "^3.6.0",
7777
"algoliasearch": "^4.25.2",
7878
"axios": "^1.16.1",
7979
"critters": "^0.0.23",
8080
"feed": "^4.2.2",
81-
"ioredis": "^5.6.1",
81+
"ioredis": "^5.10.1",
8282
"js-md5": "^0.8.3",
8383
"js-sha256": "^0.11.1",
8484
"memory-cache": "^0.2.0",

pages/_app.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getBaseLayoutByTheme } from '@/themes/theme'
1212
import { useRouter } from 'next/router'
1313
import { useCallback, useEffect, useMemo } from 'react'
1414
import { getQueryParam } from '../lib/utils'
15+
import ErrorHandler from '@/lib/utils/errorHandler'
1516

1617
// 各种扩展插件 这个要阻塞引入
1718
import BLOG from '@/blog.config'
@@ -23,6 +24,13 @@ import dynamic from 'next/dynamic'
2324
const ClerkProvider = dynamic(() =>
2425
import('@clerk/nextjs').then(m => m.ClerkProvider)
2526
)
27+
const AppErrorBoundary = ErrorHandler.createErrorBoundary(
28+
<div style={{ padding: '2rem', textAlign: 'center', minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
29+
<h1 style={{ fontSize: '1.5rem', marginBottom: '1rem' }}>Something went wrong</h1>
30+
<p style={{ color: '#666', marginBottom: '1.5rem' }}>An unexpected error occurred. Please refresh the page.</p>
31+
<button onClick={() => window.location.reload()} style={{ padding: '0.5rem 1.5rem', cursor: 'pointer', border: '1px solid #ccc', borderRadius: '4px', background: 'transparent' }}>Refresh</button>
32+
</div>
33+
)
2634

2735
/**
2836
* App挂载DOM 入口文件
@@ -75,13 +83,15 @@ const MyApp = ({ Component, pageProps }) => {
7583

7684
const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
7785
const content = (
78-
<GlobalContextProvider {...pageProps}>
79-
<GLayout {...pageProps}>
80-
<SEO {...pageProps} />
81-
<Component {...pageProps} />
82-
</GLayout>
83-
<ExternalPlugins {...pageProps} />
84-
</GlobalContextProvider>
86+
<AppErrorBoundary>
87+
<GlobalContextProvider {...pageProps}>
88+
<GLayout {...pageProps}>
89+
<SEO {...pageProps} />
90+
<Component {...pageProps} />
91+
</GLayout>
92+
<ExternalPlugins {...pageProps} />
93+
</GlobalContextProvider>
94+
</AppErrorBoundary>
8595
)
8696
return (
8797
<>

pages/api/cache.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,20 @@ import { cleanCache } from '@/lib/cache/local_file_cache'
66
* @param {*} res
77
*/
88
export default async function handler(req, res) {
9+
if (req.method !== 'POST') {
10+
return res.status(405).json({ status: 'error', message: 'Method not allowed' })
11+
}
12+
13+
const token = process.env.CACHE_REVALIDATION_TOKEN
14+
if (token && req.headers.authorization !== `Bearer ${token}`) {
15+
return res.status(401).json({ status: 'error', message: 'Unauthorized' })
16+
}
17+
918
try {
1019
await cleanCache()
1120
res.status(200).json({ status: 'success', message: 'Clean cache successful!' })
1221
} catch (error) {
13-
res.status(400).json({ status: 'error', message: 'Clean cache failed!', error })
22+
console.error('Cache clean error:', error)
23+
res.status(400).json({ status: 'error', message: 'Clean cache failed!' })
1424
}
1525
}

pages/api/subscribe.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ export default async function handler(req, res) {
1010
const { email, firstName, lastName } = req.body
1111
try {
1212
const response = await subscribeToMailchimpApi({ email, first_name: firstName, last_name: lastName })
13-
const data = await response.json()
14-
console.log('data', data)
13+
await response.json()
1514
res.status(200).json({ status: 'success', message: 'Subscription successful!' })
1615
} catch (error) {
17-
res.status(400).json({ status: 'error', message: 'Subscription failed!', error })
16+
console.error('Subscription error:', error)
17+
res.status(400).json({ status: 'error', message: 'Subscription failed!' })
1818
}
1919
} else {
2020
res.status(405).json({ status: 'error', message: 'Method not allowed' })

0 commit comments

Comments
 (0)