- Published on
NextJS 零成本建站(含管理系统)
- Authors
- Name
- Prover
next 零成本建站
要使用的技术栈:next + supabase(auth/database) 部署服务器:vecel(服务器/CI/CD)
创建一个 next 项目
参考官网的文章
直接命令行创建!
npx create-next-app@latest
然后会让你选配置
# 项目名 What is your project named? my-app # 是否使用 TS Would you like to use TypeScript? No / Yes # 是否使用 EsLint Would you like to use ESLint? No / Yes # 是否使用 Tailwind CSS(推荐一下,不用自己想类名很爽hhh) Would you like to use Tailwind CSS? No / Yes # 要不要把建一个 src 文件夹来放代码(额,没试过选 no) Would you like to use `src/` directory? No / Yes # !!!! 很重要,app router 是新推出的(坑会多一点,本文也是基于这个),no 的话就还是 pages router Would you like to use App Router? (recommended) No / Yes # 是否要自定义 @/* 作为默认的引入前缀(从 src/ 出发),选 no 即可 Would you like to customize the default import alias (@/*)? No / Yes # 上面选 yes 这里就会让你自己配置 What import alias would you like configured? @/*
等待一会,会自动安装依赖,然后进目录就可以启动啦
npm run dev [-- -p 更换默认端口号]
数据库
使用 supabase 作为免费的 Postgres 数据库存储(包括 500MB的数据存储 10,000条Postgres行的限制 50个并发连接)
安装依赖
npm install @supabase/supabase-js @supabase/ssr
找个喜欢的位置(我选的是
src/lib/supabase
)创建一下这两个文件server.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr"; import { cookies } from "next/headers"; export const supabase = () => { // 这里因为 cookies 不是动态的(每次请求都会自动获取),所以需要每次都要手动调用 const cookieStore = cookies(); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { get(name: string) { return cookieStore.get(name)?.value; }, set(name: string, value: string, options: CookieOptions) { try { cookieStore.set({ name, value, ...options }); } catch (error) { console.warn("ss-set-cookie-error", error); // The `set` method was called from a Server Component. // This can be ignored if you have middleware refreshing // user sessions. } }, remove(name: string, options: CookieOptions) { try { cookieStore.set({ name, value: "", ...options }); } catch (error) { // The `delete` method was called from a Server Component. // This can be ignored if you have middleware refreshing // user sessions. } }, }, } ); };
client.ts
import { createBrowserClient } from "@supabase/ssr"; export const ccSupabase = () => createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! );
- 关于这里为什么要创建两种客户端,可能刚学 next app router 的不知道,next app router 分为服务器组件和客户端组件,所以就需要创建两种对应的客户端
在
src/
目录下创建一个middleware.ts
(位置是固定的, 这个文件就相当于 next 请求的拦截器)import { NextResponse, type NextRequest } from "next/server"; import { createServerClient, type CookieOptions } from "@supabase/ssr"; export async function middleware(request: NextRequest) { const response = NextResponse.next({ request: { headers: request.headers, }, }); const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { get(name: string) { return request.cookies.get(name)?.value; }, set(name: string, value: string, options: CookieOptions) { request.cookies.set({ name, value, ...options }); response = NextResponse.next({ request: { headers: request.headers, }, }) response.cookies.set({ name, value, ...options }); }, remove(name: string, options: CookieOptions) { request.cookies.set({ name, value: "", ...options }); response = NextResponse.next({ request: { headers: request.headers, }, }) response.cookies.set({ name, value: "", ...options }); }, }, } ); await supabase.auth.getUser(); return response; } export const config = { // Skip all paths that should not be internationalized. // This skips the folders "api", "_next" and all files // with an extension (e.g. favicon.ico) matcher: ["/((?!api|_next|.*\\..*).*)"], };
国际化使用 next-itnl 整合 supabase 需要修改 response 的获取
TODO:挖坑
去 supabase 上创建一个项目并进入到对应的 HOME 看板页,右上角有个 connect 按钮
点进去 -> App Frameworks -> Nextjs / App router / supabase-js 就可以获取到相关的 NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_ANON_KEY 复制下来保存到项目根目录下的
.env.local
通过 supabase 的 Table Editor 面板创建表格和列
接下来就可以通过 supabase 的客户端进行数据的 CRUD,具体的可以看[官方文档](Supabase Javascript Client - Fetch data)
认证
这里直接用的 supabase auth 提供的 ui,手写 UI 的会不一样
安装下依赖
npm i @supabase/auth-ui-react @supabase/auth-ui-shared
引入 Auth 组件并配置客户端连接
"use client"; import { ccSupabase } from "@/lib/supabase/client"; import { Auth } from "@supabase/auth-ui-react"; export default function AuthModal() { return ( <Auth supabaseClient={ccSupabase()} /> ) }
[修改主题]
import { ThemeSupa } from "@supabase/auth-ui-shared"; <Auth supabaseClient={ccSupabase()} appearance={{ theme: ThemeSupa, // 如果要覆盖默认样式的话需要通过覆盖 css 变量来实现 variables: { default: { colors: { brand: "rgba(96,165,250,0.88)", brandAccent: "rgb(96,165,250)", }, }, }, }} />
- 关于修改样式的 css 变量名,可以通过 F12 调试查看对应的变量名是什么,再通过[这里](auth-ui/packages/shared/src/theming/Themes.ts at main · supabase-community/auth-ui (github.com))找到对应的属性然后覆盖即可
[关闭默认链接的显示]
{/* showLinks: 是否显示以下链接 没有帐户?报名 已经有帐户?登入 发送魔法链接电子邮件 忘记密码了吗? */} <Auth showLinks={false} ...
[使用魔方链接代替邮箱密码注册]
<Auth view="magin_link" ...
[修改重定向的位置!]
新增 redirectTo 属性为指定的路径
<Auth redirectTo="http://localhost:3858/auth/callback" ...
在对应的路由下新建一个
route.ts
, 比如我这里是/auth/callback
对应的文件路径就是/app/auth/callback/route.ts
route.ts
:nextjs 用来处理对应路由下的请求,类似于一个小型的 middlewareimport { NextResponse } from "next/server"; import { supabase } from "@/lib/supabase/server"; export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url); const code = searchParams.get("code"); const next = searchParams.get("next") ?? "/"; if (code) { // 这里主要是通过 code 调用 exchangeCodeForSession 方法获取 token 并保存 const { error } = await supabase().auth.exchangeCodeForSession(code); if (!error) { // 没有任何错误就跳转到之前的页面或者首页 return NextResponse.redirect(`${origin}${next}`); } } // 有错误就跳转到对应的处理页 return NextResponse.redirect(`${origin}/auth/auth-code-error`); }
页面获取客户信息
import { User } from "@supabase/supabase-js"; import { ccSupabase } from "@/lib/supabase/client"; import { useEffect, useState } from "react"; const [user, setUser] = useState<User | null>(null); useEffect(() => { async function init() { const { data } = await ccSupabase().auth.getUser(); setUser(data.user); } init(); }, []);
Github 登录
supabase 支持很多种社交登录,也可以在这里看官方的[文档](Auth | Supabase Docs)
先去 supabase 对应项目中的 Authentication 面板,选择 Providers 打开 Github 的开关就可以得到 Callback URL
在 github 的个人账户上创建一个 OAuth APP,输入对应的 Callback URL 和 Homepage URL,就可以得到 ClientID 和 ClientSecret
在回到刚刚获取 Callback URL 的位置,输入得到的 ClientID 和 ClientSecret 即可
然后再对应的 Auth 组件的 providers 填入 github 即可
<Auth providers={["github"]}
部署
这一步非常简单,只要再 Vecel 创建账户,关联仓库就可以,可以先本地打包看一下有没有一些报错
如果要添加生产环境的变量,可以在对应的 Vecel 项目面板中选择 Settings -> Environment Variables,在这里引入环境变量