用 Next.js 和 Supabase 进行“全栈”开发的入门

2024-05-10 17:28

本文主要是介绍用 Next.js 和 Supabase 进行“全栈”开发的入门,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • (零)前言
  • (一)创建Next.js应用程序
    • (1.1)新建工程目录
    • (1.2)安装依赖环境
    • (1.3)创建Tailwind配置
  • (二)创建Supabase项目
  • (三)Next.js应用的Supabase配置
  • (四)Next.js应用的页面
    • (4.1)/pages/_app.js
    • (4.2)/pages/index.js
    • (4.3)/pages/profile.js
    • (4.4)/pages/create-post.js
    • (4.5)/pages/posts/[id].js
  • (五)运行

(零)前言

没正经做过WEB开发。
刚初步了解html,css,javascript,然后试了下electron,就跳跃到这里了。

主要参考参考了这篇: 《使用 Next.js 和 Supabase 进行全栈开发》 <- 细节都请参考它(简称:原教程)。
本以为可以无脑复制粘贴,结果因为自己太不熟悉,以及版本变化,遇到了不少问题,特此记录。

概念:

  • 💡Next.js :基于 Node.js (中文) 的 React框架,它提供了服务器渲染、静态站点生成、路由、优化等高级功能。
  • 💡Supabase :开源的 Firebase 替代品。使用 Postgres 数据库、身份验证、即时 API、边缘函数、实时订阅、存储和矢量嵌入。

(一)创建Next.js应用程序

按照上面文章里的例子,完成个类似论坛的WEB项目。
可以做到注册发帖,前端使用Next.js,后端使用Supabase。
首先我们需要新建前端项目。

(1.1)新建工程目录

概念:

  • 💡NPM = Node(javascript) Package Manager,就是包管理器,类似python的pip呢。
  • 💡NPX = Node Package eXecute,包执行器,是npm5.2后带的命令行工具,用来执行包指令。

执行命令(我这里已经安装好node.js的)。

PS D:\XXX> npx create-next-app shion-forum

⚠️注意
因为太不熟悉,所以需要和文章中的目录结构相同,创建项目时需要选择如下:
先不使用App Router,也不使用TypeScript,这样就和作者的例子目录与文件结构一致了。
在这里插入图片描述

(1.2)安装依赖环境

cd .\shion-forum\
PS D:\XXX\shion-forum> npm install --legacy-peer-deps @supabase/supabase-js @supabase/ui react-
PS D:\XXX\shion-forum> npm install --legacy-peer-deps tailwindcss@latest @tailwindcss/typography 

⚠️注意
使用--legacy-peer-deps是因为依赖的版本冲突,从npm7.0开始,需要指定这个参数才能忽略冲突。
直接npm install会有一堆报错,类似如下:
在这里插入图片描述

(1.3)创建Tailwind配置

概念:

  • 💡Tailwind:实用程序优先的 CSS 框架,包含flex, pt-4, text-center, rotate-90等类,可以直接在标记中构建任何设计。

执行指令初始化文件:

PS D:\XXX\shion-forum> npx tailwindcss init -p

更新tailwind.config.js文件中这部分:

plugins: [require('@tailwindcss/typography')
]

再将styles/globals.css中的样式替换为以下内容(多的删掉)。

@tailwind base;
@tailwind components;
@tailwind utilities;

(二)创建Supabase项目

然后去Supabase.io创建一个官网托管的项目。

这部分没有需要注意或修改的,
可以完全参考: 原教程

建表脚本中可以看出,有记录级别的校验。
用户新建的帖子,只有用户自己可以修改删除。
但任何用户都可以查看所有的帖子。

CREATE TABLE posts (id bigint generated by default as identity primary key,user_id uuid references auth.users not null,user_email text,title text,content text,inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);alter table posts enable row level security;create policy "Individuals can create posts." on posts forinsert with check (auth.uid() = user_id);create policy "Individuals can update their own posts." on posts forupdate using (auth.uid() = user_id);create policy "Individuals can delete their own posts." on posts fordelete using (auth.uid() = user_id);create policy "Posts are public." on posts forselect using (true);

(三)Next.js应用的Supabase配置

在项目的根目录创建.env.local文件,并添加以下配置。

NEXT_PUBLIC_SUPABASE_URL= %YOUR_PROJECT_URL%
NEXT_PUBLIC_SUPABASE_ANON_KEY= %YOUR_PROJECT_API_KEY%

上面的%YOUR_PROJECT_URL%%YOUR_PROJECT_API_KEY%这两个地方。
需要填写的内容到Supabase网站你的托管项目中的:settings -> API 中查看,并修改上面文件的内容为URLAPI KEY的实际值。
在这里插入图片描述

然后在项目的根目录创建api.js文件,并添加以下代码:

// api.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL,process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

(四)Next.js应用的页面

这部分代码由于原教程用的 supabase api 版本(v1)和现在(v2)不一样。
所以一些代码经过修改才能正常用,文件和代码如下:

(4.1)/pages/_app.js

// pages/_app.js
import Link from 'next/link'
import Head from 'next/head'
import { useState, useEffect } from 'react'
import { supabase } from '../api'
import '../styles/globals.css'function MyApp({ Component, pageProps }) {const [session, setSession] = useState(null)const [user, setUser] = useState(null);async function checkUser() {const {data: { user },} = await supabase.auth.getUser()setUser(user)}useEffect(() => {supabase.auth.getSession().then(({ data: { session } }) => {setSession(session)})const {data: { subscription },} = supabase.auth.onAuthStateChange((_event, session) => {setSession(session)checkUser()})checkUser()return () => subscription.unsubscribe()}, [])return (<div><Head><title>Shion's Test forum</title><meta property="og:title" content="My page title" key="title" /></Head><nav className="p-6 border-b border-gray-300 flex-auto text-lg font-semibold text-sky-500 dark:text-sky-400"><Link href="/"><span className="mr-6 cursor-pointer">首页(Home)</span></Link>{session && user && (<Link href="/create-post"><span className="mr-6 cursor-pointer">新帖(Create Post)</span></Link>)}<Link href="/profile"><span className="mr-6 cursor-pointer">用户(Profile)</span></Link></nav>{session && user && (<div className="py-2 px-16"><span className="text-m font-semibold">登录用户(login User) {user.email}</span></div>)}<div className="py-8 px-16"><Component {...pageProps} /></div></div>)
}export default MyApp

(4.2)/pages/index.js

主界面,显示帖子列表。

// pages/index.js
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { supabase } from '../api'export default function Home() {const [posts, setPosts] = useState([])const [loading, setLoading] = useState(true)useEffect(() => {fetchPosts()}, [])async function fetchPosts() {const { data, error } = await supabase.from('posts').select()setPosts(data)setLoading(false)}if (loading) return <p className="text-2xl">载入帖子中(Loading)...</p>if (!posts.length) return <p className="text-2xl">完全是空的(No posts.)</p>return (<div><h1 className="text-3xl font-semibold tracking-wide mt-6 mb-2">帖子(Posts)</h1>{posts.map(post => (<Link key={post.id} href={`/posts/${post.id}`}><div className="cursor-pointer border-b border-gray-300	mt-8 pb-4"><h2 className="text-xl font-semibold">{post.title}</h2><p className="text-gray-500 mt-2">{post.user_email}</p></div></Link>))}</div>)
}

(4.3)/pages/profile.js

用户登录界面,直接使用了supabase的auth。
可通过注册邮箱来登录系统。

// pages/profile.js
import { Typography, Button } from "@supabase/ui";
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const { Text } = Typography
import { supabase } from '../api'function Profile(props) {const { user } = Auth.useUser()if (user)return (<><div className="w-80 shadow rounded"><Text>登录用户(Signed in): {user.email}</Text><Button block onClick={() => props.supabaseClient.auth.signOut()}>退出登录(Sign out)</Button></div></>);return props.children
}export default function AuthProfile() {return (<Auth.UserContextProvider supabaseClient={supabase}><Profile supabaseClient={supabase}><Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} /></Profile></Auth.UserContextProvider>)
}

(4.4)/pages/create-post.js

发新帖界面。

// pages/create-post.js
import { useState } from 'react'
import { v4 as uuid } from 'uuid'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import "easymde/dist/easymde.min.css"
import { supabase } from '../api'const SimpleMDE = dynamic(() => import('react-simplemde-editor'), { ssr: false })
const initialState = { title: '', content: '' }function CreatePost() {const [post, setPost] = useState(initialState)const { title, content } = postconst router = useRouter()function onChange(e) {setPost(() => ({ ...post, [e.target.name]: e.target.value }))}async function createNewPost() {if (!title || !content) returnconst {data: { user },} = await supabase.auth.getUser()const id = uuid()post.id = idconst { data } = await supabase.from('posts').insert([{ title, content, user_id: user.id, user_email: user.email }]).select().single()router.push(`/posts/${data.id}`)}return (<div><h1 className="text-3xl font-semibold tracking-wide mt-6">发新帖(Create new post)</h1><inputonChange={onChange}name="title"placeholder="Title"value={post.title}className="border-b pb-2 text-lg my-4 focus:outline-none w-full font-light text-gray-500 placeholder-gray-500 y-2"/><SimpleMDEvalue={post.content}options={{spellChecker: false,toolbar: ['bold','italic','heading','|','quote','code','table','horizontal-rule','unordered-list','ordered-list','|','link','image','|','side-by-side','fullscreen','|','guide']}}onChange={value => setPost({ ...post, content: value })}/><buttontype="button"className="mb-4 bg-green-600 text-white font-semibold px-8 py-2 rounded-lg"onClick={createNewPost}>提交(Submit)</button></div>)
}export default CreatePost

(4.5)/pages/posts/[id].js

单个帖子查看界面。
这里需要动态的创建页面。

// /pages/posts/[id].js
import { useRouter } from 'next/router'
import ReactMarkdown from 'react-markdown'
import { supabase } from '../../api'export default function Post({ post }) {const router = useRouter()if (router.isFallback) {return <div>Loading...</div>}return (<div><h1 className="text-5xl mt-4 font-semibold tracking-wide">{post.title}</h1><p className="text-sm font-light my-4">by {post.user_email}</p><div className="mt-8"><ReactMarkdown className='prose' children={post.content} /></div></div>)
}export async function getStaticPaths() {const { data, error } = await supabase.from('posts').select('id')const paths = data.map(post => ({ params: { id: JSON.stringify(post.id) } }))return {paths,fallback: true}
}export async function getStaticProps({ params }) {const { id } = paramsconst { data } = await supabase.from('posts').select().filter('id', 'eq', id).single()return {props: {post: data}}
}

(五)运行

项目目录中执行:

PS D:\XXX\shion-forum>  npm run dev> shion-forum@0.1.0 dev
> next dev▲ Next.js 14.2.3- Local:        http://localhost:3000- Environments: .env.local✓ Starting...✓ Ready in 4.2s○ Compiling / ...✓ Compiled / in 2.5s (359 modules)
……

然后浏览器访问http://localhost:3000
初始状态如下图:
在这里插入图片描述


注册用户,登录。
然后发一些帖子后,
主界面如下图:
在这里插入图片描述


发帖后的帖子展示,同理首页点击某个帖子标题后。
如下图:
在这里插入图片描述
至此基本功能就OK了。
原教程后面部分内容就懒得弄了。
PS:为什么我的MD编辑器按钮都没图标……😢

这篇关于用 Next.js 和 Supabase 进行“全栈”开发的入门的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977088

相关文章

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

从入门到精通详解Python虚拟环境完全指南

《从入门到精通详解Python虚拟环境完全指南》Python虚拟环境是一个独立的Python运行环境,它允许你为不同的项目创建隔离的Python环境,下面小编就来和大家详细介绍一下吧... 目录什么是python虚拟环境一、使用venv创建和管理虚拟环境1.1 创建虚拟环境1.2 激活虚拟环境1.3 验证虚

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

Nginx中配置使用非默认80端口进行服务的完整指南

《Nginx中配置使用非默认80端口进行服务的完整指南》在实际生产环境中,我们经常需要将Nginx配置在其他端口上运行,本文将详细介绍如何在Nginx中配置使用非默认端口进行服务,希望对大家有所帮助... 目录一、为什么需要使用非默认端口二、配置Nginx使用非默认端口的基本方法2.1 修改listen指令

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

MySQL按时间维度对亿级数据表进行平滑分表

《MySQL按时间维度对亿级数据表进行平滑分表》本文将以一个真实的4亿数据表分表案例为基础,详细介绍如何在不影响线上业务的情况下,完成按时间维度分表的完整过程,感兴趣的小伙伴可以了解一下... 目录引言一、为什么我们需要分表1.1 单表数据量过大的问题1.2 分表方案选型二、分表前的准备工作2.1 数据评估

Java List 使用举例(从入门到精通)

《JavaList使用举例(从入门到精通)》本文系统讲解JavaList,涵盖基础概念、核心特性、常用实现(如ArrayList、LinkedList)及性能对比,介绍创建、操作、遍历方法,结合实... 目录一、List 基础概念1.1 什么是 List?1.2 List 的核心特性1.3 List 家族成

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块