技术栈
前端:React, Next.js, Tailwind CSS , TypeScript
后端-CMS:Sanity
效果展示
项目启动步骤
运行开发环境
- 运行
npm run dev
打开后台内容管理系统:Sanity Studio
- 进入> 项目文件夹(前端,含 package.json) >Sanity 项目文件夹 (后端,含 package.json)
- 输入
sanity login>sanity start - 成功提示:
打开后台网址并登录:http://localhost:3333
- 成功提示:
- 可以看到提供的模板表单条目(schemas),可个性化设计调整
后台数据库与前端连接配置
进入Sanity项目文件夹> /schemas> post.js,个性化调整表单条目与类型
- 在后台网站, 创建一条Post
- 选择Vision 标签> 输入Query指令,可以获取Post 数据
*[_type =="post"]{
_id,
title,
author->{
name,
image
},
description,
mainImage,
slug
}
安装插件 next-sanity: 便于API请求,将后台内容与前端连接
- 进入> 项目文件夹根目录
- 打开终端,输入
npm i next-sanity
在根目录,创建配置文件
- /sanity.js:将后台数据库与前端连接
- /.env.local:存放环境变量
设置env.local
- 打开/static > sanity.json, 获取相关参数
- PUBLIC 参数:客户端与API同时可见
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_PROJECT_ID=j70xi0l8
设置 sanity.js 配置文件(与firebase.js 原理类似)
import {
createImageUrlBuilder,
createCurrentUserHook,
createClient,
} from "next-sanity"; // 注意该方案将报错:ImageUrlBuilder不再受支持!
export const config = {
/*COMMENT:
* Find your project ID and dataset in `sanity.json` in your studio project.
* These are considered “public”, but you can use environment variables
* if you want differ between local dev and production.
*
* https://nextjs.org/docs/basic-features/environment-variables
**/
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production",
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
apiVersion: "2021-08-11", // or today's date for latest
/**
* Set useCdn to `false` if your application require the freshest possible
* data always (potentially slightly slower and a bit more expensive).
* Authenticated request (like preview) will always bypass the CDN
**/
useCdn: process.env.NODE_ENV === "production",
};
// COMMENT: Set up the client for fetching data in the getProps page function
export const sanityClient = createClient(config);
// COMMENT: Set up a helper function for generating Image URLs with only the asset reference data in your documents
export const urlFor = (source) => createImageUrlBuilder(config).image(source);
// COMMENT: Helper function for using the current logged in user account
export const useCurrentUser = createCurrentUserHook(config);
- ❌! 注意 next-sanity 不再支持 createImageUrlBuilder,将出现图片链接插件报错,参考:stackoverflow.com/questions/7…
- 需要另外在根目录下载 image-url:
npm install @sanity/image-url
import { createCurrentUserHook, createClient } from "next-sanity";
import createImageUrlBuilder from "./node_modules/@sanity/image-url";
博文预览卡片
数据请求:SSR-服务端渲染
- 每次请求,渲染一次:Next.js作为中间服务器,将请求页面渲染给客户端;
- 避免请求时,客户端一次加载全部内容 (bundle)
- 将页面路径(Page route)转化为服务端路径 (Server-side route)
实现:如何在Next.js 中实现SSR?
- 打开 index.tsx,添加函数 getServerSideProps,将服务端数据返回给客户端,
- 💡难点:将 props 返回给Home 函数
import { sanityClient, urlFor } from "../sanity";
/* Get the Server Side Rendered Data back to the Client*/
export const getServerSideProps = async () => {
const query = `*[_type =="post"]{
_id,
title,
author->{
name,
image
},
description,
mainImage,
slug
}`;
const posts = await sanityClient.fetch(query);
return {
props: {
posts,
},
};
};
- 💡难点:定义props 类型
-
- 根目录创建TypeScript定义文件:typings.d.ts
- 可在后台网站 Post > Inspect,查看数据类型
export interface Post {
_id: string;
_createdAt: string;
title: string;
author: {
name: string;
image: string;
};
description: string;
mainImage: {
asset: {
url: string;
};
};
slug: {
current: string;
};
body: [object];
}
-
- 定义 props 类型
interface Props {
posts: [Post];
}
export default function Home({ posts }: Props) {}
预览卡片:样式
{/* Posts */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 md:gap-6 p-2 lg:p-6">
{posts.map((post) => (
<Link key={post._id} href={`/post/${post.slug.current}`}>
<div className="group cursor-pointer border rounded-lg overflow-hidden">
<img
className="h-60 w-full object-cover group-hover:scale-105 transition-transform duration-200 ease-in-out"
src={urlFor(post.mainImage).url()!}
alt=""
/>
<div className="flex justify-between p-5 bg-white">
<div>
<p className="text-lg font-bold">{post.title}</p>
<p className="text-xs">
{post.description} by {post.author.name}
</p>
</div>
<img
className="h-12 w-12 rounded-full"
src={urlFor(post.author.image).url()!}
alt=""
/>
</div>
</div>
</Link>
))}
</div>