Next.js, Sanity 博客项目——数据请求与博文预览开发方案

437 阅读2分钟

技术栈

前端:React, Next.js, Tailwind CSS , TypeScript

后端-CMS:Sanity

效果展示

1108-Blog-Post-Gif.gif

项目启动步骤

运行开发环境

  • 运行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);
  • 需要另外在根目录下载 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>