Skip to content

Commit

Permalink
Add blog couldflare-next-on-page-edge
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaapple committed Nov 3, 2024
1 parent 727f1c8 commit 87b3b41
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 6 deletions.
6 changes: 3 additions & 3 deletions frontend/app/[locale]/(docs)/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import '@/styles/mdx.css';
import { Metadata } from 'next';

import { absoluteUrl, cn } from '@/lib/utils';
import { siteConfig } from '@/config';
import { PageGenUrl, siteConfig } from '@/config';
import { type Locale, routing } from '@/i18n/routing';
import { unstable_setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
Expand Down Expand Up @@ -112,8 +112,8 @@ export default async function DocPage({ params }: DocPageProps) {
<Link href="/" prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }), 'w-full sm:w-auto')}>
Hybrid AI Search Now
</Link>
<Link href="/" prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }), 'w-full sm:w-auto')}>
AI Generate UI Now
<Link href={PageGenUrl} prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }))}>
AI Page Generator Now
</Link>
</div>
</main>
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/[locale]/(marketing)/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Link from 'next/link';

import { absoluteUrl, cn, formatDate } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
import { siteConfig } from '@/config';
import { PageGenUrl, siteConfig } from '@/config';
import { unstable_setRequestLocale } from 'next-intl/server';
import { type Locale, routing } from '@/i18n/routing';

Expand Down Expand Up @@ -110,8 +110,8 @@ export default async function PostPage({ params }: PostPageProps) {
<Link href="/" prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }))}>
Hybrid AI Search Now
</Link>
<Link href="/" prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }))}>
AI Generate UI Now
<Link href={PageGenUrl} prefetch={false} className={cn(buttonVariants({ size: 'lg', rounded: 'full' }))}>
AI Page Generator Now
</Link>
</div>

Expand Down
133 changes: 133 additions & 0 deletions frontend/content/blog/en/couldflare-next-on-page-edge.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: How to migrate MemFree from vercel to cloudflare next-on-pages
description: Issues and solutions for migrating MemFree from vercel to cloudflare next-on-pages
image: /images/blog/blog-post-3.jpg
date: '2024-11-03'
---

## Deploying a Static Next.js App on Cloudflare Pages

Deploying a static Next.js application on Cloudflare Pages is straightforward. You can accomplish this with just a few commands by following the guide on [Cloudflare's documentation for deploying a static Next.js site](https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-static-nextjs-site/).

To validate this process, I first extracted the React review feature from the MemFree AI Page Generator and deployed it as a standalone webpage: [React Shadcn UI Preview](https://reactshadcn.com/).

The entire deployment process was smooth, with minimal obstacles encountered.

## Deploying MemFree AI Page Generator on Cloudflare

You can experience the online version here: [PageGen - AI Page Generator](https://pagegen.ai/).

1. Challenge 1: All APIs Support for Edge Runtime

2. Challenge 2: All Dynamic Pages Support for Edge Runtime

## Specific Cloudflare Page Edge Compatibility Issues

### 1. Custom Format Replacement for `util.format`

To ensure compatibility with the edge environment, replace the `util.format` function with the following implementation:

```ts
// Replace the util.format function with this one; this method works in the edge environment
export function format(template, ...args) {
if (args.length === 0) return template;

if (args.length === 1 && typeof args[0] === 'object') {
return template.replace(/%[sdj]/g, (match) => String(args[0]));
}

let index = 0;
return template.replace(/%[sdj]/g, () => {
if (index >= args.length) return '';
const arg = args[index++];
return String(arg);
});
}
```

### 2. Replace `@upstash/redis` with `@upstash/redis/cloudflare`

Using `@upstash/redis` directly may result in the following error:

```ts
Failed to get searches: Error: The 'cache' field on 'RequestInitializerDict' is not implemented
```

To resolve this, simply replace the import statement:

```ts
import { Redis } from '@upstash/redis/cloudflare';
```

No other code modifications are necessary.

### 3. Replace `pdfjs` with `unpdf`

The `pdfjs` library, widely used for PDF parsing, is incompatible with the edge environment. If you attempt to use `pdfjs`, you may encounter the following errors during deployment:

```ts
⚡️ Unexpected error: Build failed with 4 errors:
⚡️ <stdin>:883:64363: ERROR: Could not resolve "fs"
⚡️ <stdin>:883:64423: ERROR: Could not resolve "http"
⚡️ <stdin>:883:64450: ERROR: Could not resolve "https"
⚡️ <stdin>:883:64476: ERROR: Could not resolve "url"
```

The reason for these errors is that the edge environment does not support certain Node.js modules. The solution is to use [unpdf](https://github.com/unjs/unpdf) instead.

### 4. Replace `uploadthing` with R2

`uploadthing` is a component designed to simplify client-side file uploads across various frameworks, but it does not support the Cloudflare edge environment.

When using Cloudflare Pages or Workers, you can easily upload files to R2 with just a few lines of code, benefiting from free CDN acceleration:

```ts
const res = await getRequestContext().env.IMAGES.put(safeFileName, file);
```

### 5. Use Static Variables Instead of `NEXT_PUBLIC` Environment Variables

In Cloudflare's Next-on-Pages, `NEXT_PUBLIC` environment variables cannot be accessed directly. You can replace them with static variables instead.

### 6. Replace `contentlayer2` with `next/mdx`

The documentation and blog sections of MemFree were generated using `contentlayer2` based on MDX files. However, using `contentlayer2` in Cloudflare Next-on-Pages results in the following error:

```ts
EvalError: Code generation from strings disallowed for this context
```

This limitation is unreasonable, as static pages can be fully generated during the build process without needing runtime generation. Ultimately, I replaced `contentlayer2` with `next/mdx`, allowing for complete static page generation during compilation.

For more details, refer to [Markdown and MDX](https://nextjs.org/docs/pages/building-your-application/configuring/mdx).

You can see the results on PageGen's [Privacy Policy](https://pagegen.ai/privacy) and [Terms of Service](https://pagegen.ai/terms) pages.

### 7. Use Bundle Analyzer to Reduce Bundle Size

One issue arises when enabling edge runtime for dynamic pages, as some large client dependencies may inadvertently enter the runtime's dependency tree:

```ts
__next-on-pages-dist__/functions/search/[id].func.jsesm8995.45 KiB
```

For instance, the size of the search page can reach around 8 MB. By analyzing the bundle, we identified two large client packages: `@babel/standalone` and `heic2any`.

We addressed this by dynamically importing these packages, enabling lazy loading and singleton instantiation, which reduced the overall project size from 25 MB to just 2 MB.

### 8. Use Cloudflare Image Loader

Integrating Cloudflare's Image Loader is straightforward. Follow the steps outlined in [Cloudflare Image Transform integration with Next.js](https://developers.cloudflare.com/images/transform-images/integrate-with-frameworks/). The core steps include:

1. Defining `imageLoader.ts` in your code.
2. Enabling Cloudflare's image transformation service for your domain [Cloudflare Image Transform Image](https://developers.cloudflare.com/images/get-started/).

### 9. Domain Binding and Redirection

The domain binding and redirection settings differ between Cloudflare Pages and Vercel. For a Pages project, you need to bind both the www and root domains to the Pages project. Then, use Cloudflare's built-in redirection rules to redirect the www domain to the root domain.

## Online Experience

You can explore the live version here: [PageGen - AI Page Generator](https://pagegen.ai/).

All source code for MemFree is open-source, and contributions are welcome! Feel free to give it a star on GitHub: [MemFree GitHub](https://github.com/memfreeme/memfree).
125 changes: 125 additions & 0 deletions frontend/content/blog/zh/couldflare-next-on-page-edge.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: How to migrate MemFree from vercel to cloudflare next-on-pages
description: Issues and solutions for migrating MemFree from vercel to cloudflare next-on-pages
image: /images/blog/blog-post-3.jpg
date: '2024-11-03'
---

## Deploy Static Nextjs App on cloudflare page is very easy

你只需要参考 https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-static-nextjs-site/ 几行命令就可以搞定。

为了实践验证,我首先将 MemFree AI Page generator 中的react review 功能拆分出来,单独部署成一个网页: [React Shadcn UI Preview](https://reactshadcn.com/)

整个过程十分流程,基本上没有阻碍

## 将 MemFree AI Page Generator 部署到 cloudflare

### 挑战一:API 需要支持 edge runtime

### 挑战二:动态页面需要支持 edge runtime

## 具体的 edge 兼容性问题

### 1 自定义的 format 替换 util.format

```ts
// Replace the util.format function with this one, this method could work on edge environment
export function format(template, ...args) {
if (args.length === 0) return template;

if (args.length === 1 && typeof args[0] === 'object') {
return template.replace(/%[sdj]/g, (match) => String(args[0]));
}

let index = 0;
return template.replace(/%[sdj]/g, () => {
if (index >= args.length) return '';
const arg = args[index++];
return String(arg);
});
}
```

### 2 @upstash/redis/cloudflare 替换 @upstash/redis

直接使用 @upstash/redis 会得到下面的报错:

```ts
Failed to get searches: Error: The 'cache' field on 'RequestInitializerDict' is not implemented
```

只需要在 import 中用 @upstash/redis/cloudflare 替换 @upstash/redis, 其他代码都不需要改

```ts
import { Redis } from '@upstash/redis/cloudflare';
```

### 3 unpdf 替换 pdfjs dist

pdfjs 是一个使用比较广泛的 PDF 解析库,但是在edge 环境无法运行。 使用 pdfjs, 在执行
`bun run deploy` 时,你会得到下面的报错:

```ts
⚡️ Unexpected error: Build failed with 4 errors:
⚡️ <stdin>:883:64363: ERROR: Could not resolve "fs"
⚡️ <stdin>:883:64423: ERROR: Could not resolve "http"
⚡️ <stdin>:883:64450: ERROR: Could not resolve "https"
⚡️ <stdin>:883:64476: ERROR: Could not resolve "url"
```

原因是 edge 环境不支持对应node 模块。

解决方案是使用 [unpdf](https://github.com/unjs/unpdf)

### 4 R2 替换 uploadthing

uploadthing 是一款简化客户端文件上传的组件,对各种流行的框架进行了封装,简化使用,但是部支持 Cloudflare edge 环境。

当使用 cloudflare 的page 或者worker 时,几行代码就可以将文件上传到 R2 上,并免费获得CDN 加速, 十分简单,性能也不错。

```ts
const res = await getRequestContext().env.IMAGES.put(safeFileName, file);
```

### 5 用静态变量替换 NEXT_PUBLIC env 变量

NEXT_PUBLIC 的 env 变量在 cloudflare next-on-pages 中无法直接访问,可以直接用静态变量替换

### 6 用 next/mdx 替换 contentlayer2

memfree的doc和blog 都是利用contentlayer2 基于mdx文件生成的静态页面, 使用 contentlayer2,在 cloudflare next-on-pages 中会得到下面的报错:

```ts
EvalError: Code generation from strings disallowed for this context
```

但是这一点其实是不合理的,因为是静态页面完全可以在build期间全部生成,不需要运行时动态生成。 最后我使用 next/mdx 替换了 contentlayer2,在编译期间将 mdx 完全替换成静态页面。

大家可以参考 [Markdown and MDX](https://nextjs.org/docs/pages/building-your-application/configuring/mdx)

具体的效果可以参考 PageGen 的 [PageGen Privacy Policy](https://pagegen.ai/privacy)[PageGen Terms of Service](https://pagegen.ai/terms) 页面。

### 7 利用 bundle-analyzer 减小打包大小

这里会出问题的原因时,当你对一些动态页面开启edge runtime时,一些很大的客户端依赖进入了runtime的依赖。

```ts
__next-on-pages-dist__/functions/search/[id].func.jsesm8995.45 KiB
```

例如上面这个示例,一个search 页面大小显示 8 M 左右,经过bundle-analyzer 分区edge 环境的包依赖后,就会两个很大的客户端包:`@babel/standalone``heic2any`

然后我们就这两个包进行动态导入,延迟加载,单例化,整个项目的总体积直接从25M降低到2M。

### 8 使用 cloudflare Image Loader

这个比较简单,参考 [Cloudflare Image Transform integrate nextjs](https://developers.cloudflare.com/images/transform-images/integrate-with-frameworks/) 进行处理就行。 核心就是2步:

1. 代码中定义 `imageLoader.ts`
2. 对域名启用 Cloudflare image transformation 服务 [Cloudflare Image Transform Image](https://developers.cloudflare.com/images/get-started/)

### 9 域名绑定和重定向

cloudflare page和vercel的域名绑定,重定向设置不同。
对于一个page 项目,我们需要将 带www的域名和根域名都绑定到 page 项目,然后利用couldflare 自带的重定向规则,将www的域名 重定向到根域名。

0 comments on commit 87b3b41

Please sign in to comment.