使用 Hashnode API、Typescript 和 GraphQL 将博客文章添加到您的 React 站点

news2024/11/15 11:40:50

在本文中,我们将:

  • 使用 Next.js 引导一个 React.js Typescript 项目。

  • 设置 Apollo GraphQL 客户端并将其集成到我们的项目中。

  • 设置 GraphQL Codegen 以生成我们可以在整个应用程序中使用的类型、类型安全查询和自定义挂钩。

  • 创建一个索引页面,其中包含我们所有帖子的列表。

  • 为我们所有的帖子生成单独的静态博客页面。

我们不会:

  • 进入 Next.js 的基础知识。

  • 做很多造型。

  • 当新帖子添加到您的 Hashnode 时,设置一个 webhook 来重建我们的网站。

你会需要:

  • 包含博客文章的 Hashnode 帐户

  • 能够运行引导的 Next.js 项目的开发环境。

引导项目和安装依赖项。

第 1 步 - 初始化一个新的 Next.js 项目

我们首先使用 TypeScript 和默认设置引导一个新的 Next.js 项目。

yarn create next-app

之后,我们可以安装必要的依赖项来实现所有的魔法。

第 2 步 - 初始化并安装 Apollo 客户端

yarn add @apollo/client graphql

之后,我创建了一个名为lib根目录的文件夹,并在其中创建了一个名为apollo.ts. 该文件将包含创建可在服务器端使用的新客户端的逻辑,因为我们不需要任何客户端获取数据。所有页面都将是静态的或服务器端呈现的。

如果您不知道它们之间的区别,我建议您阅读数据获取部分中的 Next.js 文档 👉数据获取:概述👈

lib/apollo.ts

import { ApolloClient, InMemoryCache } from"@apollo/client";exportconst createClient = () => {
  const client = new ApolloClient({
    uri: "https://api.hashnode.com/",
    cache: new InMemoryCache(),
  });return client;
};

这是他们的文档中描述的 Apollo Client 的默认初始化。在这里。唯一的区别是我将客户端指向 Hashnode API 端点,https://api.hashnode.com因为这是我们要获取帖子的地方。

第 3 步 - 安装 GraphQL CodeGen

多亏了 GraphQL 和 TypeScript 的魔力,我们可以在我们的应用程序和服务器/API 之间生成端到端类型安全的数据获取。这是一次性设置,将在未来加速开发,消除类型错误,并验证我们的模式。

运行以下命令来安装实现此目的所需的软件包:

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations

这将安装以下软件包 devDepencencies

  • @graphql-codegen/cli

  • @graphql-codegen/typescript

  • @graphql-codegen/typescript-graphql-request

  • @graphql-codegen/typescript-operations

我建议在npmjs-website上查看它们

第 4 步 - 配置 GraphQL Codegen

在项目的根目录中创建一个名为 的文件graphql.codegen.yml,该文件将保存我们代码生成的配置。

schema:-"https://api.hashnode.com"documents:-"./graphql/**/*.graphql"generates:./generated/graphql.ts:plugins:-typescript-typescript-operations

配置文件执行以下操作:

  • schema这指向我们用于获取 API 模式映射的 GraphQL 端点。

  • documents这告诉 GraphQL Codegen 在哪里寻找我们的模式文件

  • generates这告诉 GraphQL Codegen 在哪里创建和存储我们生成的代码。

  • plugins指定它应使用的 GraphQL Codegen 插件。

我们需要做的是将代码生成脚本添加到我们的package.json

在里面scripts添加这一行:

{
    "scripts": {
        ..."generate": "graphql-codegen --config ./graphql.config.yml",
        ...
    }
}

此时如果我们尝试运行生成脚本,我们将得到以下输出:

yarn generate
yarn run v1.22.15
$ graphql-codegen --config graphql.codegen.yml
(node:42491) ExperimentalWarning: stream/web is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔ Parse Configuration
⚠ Generate outputs
  ❯ Generate to ./generated/graphql.ts
    ✔ Load GraphQL schemas
    ✖
      Unable to find any GraphQL type definitions for the following pointers:
      - ./graphql/**/*.graphql
    ◼ Generate
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

即使我们在这里抛出错误,也意味着我们已经成功安装了代码生成器。错误是因为我们还没有在文件夹中定义任何 graphql 模式graphql

第 5 步 - 添加我们的第一个 GraphQL Schema

如果您前往,https://api.hashnode.com您可以使用他们的 API playground 来编写您需要的模式代码。它有文档,您可以在其中浏览可用字段等。

您还可以测试架构并查看结果。这就是它在浏览器中的样子。

我正在抓取我在 playground 中创作的模式,并将其添加到一个名为位于项目根目录中的get-blog-posts.query.graphql文件夹内的文件中。graphql

如果您现在运行生成脚本,您将看到以下输出:

yarn generate
yarn run v1.22.15
$ graphql-codegen --config graphql.codegen.yml
(node:42632) ExperimentalWarning: stream/web is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✔ Parse Configuration
✔ Generate outputs
✨  Done in 3.30s.

此外,您会看到在您的根目录中弹出一个新文件夹,其中包含generated一个名为graphql.ts. 这个漂亮的文件现在包含了我们继续构建应用程序所需的所有类型注释。

例如,我们可以查看Post如下所示的 -type:

exporttype Post = {
  __typename?: 'Post';
  _id: Scalars['ID'];
  author?: Maybe<User>;
  bookmarkedIn?: Maybe<Array<Maybe<Scalars['String']>>>;
  brief?: Maybe<Scalars['String']>;
  contentMarkdown?: Maybe<Scalars['String']>;
  contributors?: Maybe<Array<Maybe<Contributor>>>;
  coverImage: Scalars['String'];
  cuid?: Maybe<Scalars['String']>;
  dateAdded?: Maybe<Scalars['String']>;
  dateFeatured?: Maybe<Scalars['String']>;
  dateUpdated?: Maybe<Scalars['String']>;
  followersCount?: Maybe<Scalars['Int']>;
  isActive?: Maybe<Scalars['Boolean']>;
  isAnonymous?: Maybe<Scalars['Boolean']>;
  numUniqueUsersWhoReacted?: Maybe<Scalars['Int']>;
  partOfPublication?: Maybe<Scalars['Boolean']>;
  poll?: Maybe<Poll>;
  popularity?: Maybe<Scalars['Float']>;
  reactions?: Maybe<Array<Maybe<Reaction>>>;
  reactionsByCurrentUser?: Maybe<Array<Maybe<Reaction>>>;
  replyCount?: Maybe<Scalars['Int']>;
  responseCount?: Maybe<Scalars['Int']>;
  slug?: Maybe<Scalars['String']>;
  tags?: Maybe<Array<Maybe<Tag>>>;
  title?: Maybe<Scalars['String']>;
  totalReactions?: Maybe<Scalars['Int']>;
  type: Scalars['String'];
};

每次我们运行代码生成脚本时,该文件都会更新。

我们现在准备开始构建包含所有帖子列表的索引页面。

使用 getStaticProps 获取博客文章

由于我们正在构建一个静态块,因此我们将用于getStaticProps通过道具将帖子传递到我们的 React 组件中。经验丰富的 Next.js 用户将确切了解未来的工作流程。

我现在正在清除 Next.js 在目录index.tsx中的文件中为我们生成的默认代码pages。此外,我在底部通过导出一个async名为getStaticProps.

完成后它看起来像这样。

pages/index.tsx

import Head from"next/head";
import styles from"@/styles/Home.module.css";
import { createClient } from"@/lib/apollo";
import {
  GetBlogPostsDocument,
  GetBlogPostsQuery,
  GetBlogPostsQueryVariables,
  Post,
} from"@/generated/graphql";
import { NextPage } from"next";
import Link from"next/link";type Props = {
  posts: Post[];
};const HomePage: NextPage<Props> = ({ posts }) => (
  <>
    <Head>
      <title>Create Next App</title>
      <meta name="description" content="Generated by create next app" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <link rel="icon" href="/favicon.ico" />
    </Head>
    <main className={styles.main}>
      <div style={{ padding: "2rem" }}>
        <h1>My Hashnode Blog</h1>
        <p>Powered by React, Typescript and, GraphQL</p>
      </div>
      <div>
        {posts.map((post) => (
          <div key={post._id} style={{ padding: "2rem" }}>
            <p>{post.title}</p>
            <Link
              target="_blank"
              href={`/${post.slug}`}
              style={{ textDecoration: "underline", color: "pink" }}
            >
              Read more 🚀
            </Link>
          </div>
        ))}
      </div>
    </main>
  </>
);exportdefault HomePage;exportconst getStaticProps = async () => {
  const client = createClient();const { data } = await client.query<
    GetBlogPostsQuery,
    GetBlogPostsQueryVariables
  >({ query: GetBlogPostsDocument });const posts = data.user?.publication?.posts ?? [];return {
    props: {
      posts,
    },
  };
};

看!端到端类型安全,我们没有手动输入任何类型 🤩 在更大的项目中,这将节省大量时间来解决错误。

这在我的浏览器中看起来像这样:

现在我们有了我们的帖子列表,我们需要开始生成我们的个人页面,这是通过使用 Next.js 中非常常见的技术通过使用getStaticProps+ getStaticPaths+ 动态路由模式来完成的。

准备从 Hashnode API 获取单个帖子。

首先,让我们再次前往 Hashnode API playground 并验证我们获取单个帖子所需的模式。

这是我从他们的操场上抓取的模式:

之后,我在我们的graphql文件夹中创建了一个名为的新文件get-blog-post.query.graphql,我正在修改它以便我可以将变量传递给它。

query GetBlogPost($slug: String!, $hostname: String!) {
  post(slug: $slug, hostname: $hostname) {
    title
    coverImage
    slug
    contentMarkdown
    dateAdded
  }
}

其中slug是你在Hashnode网站上发帖的slug,hostname是你在Hashnode上的主地址。找到它的一种简单方法是打开顶部导航栏中的配置文件上下文菜单。

在我的例子中,主机名是carlw.hashnode.dev. 我们正在以火箭速度取得进步🚀

不要忘记通过运行以下命令来验证您的模式并生成类型:

yarn generate

为每篇博文生成单独的静态页面

pages首先,在目录中创建一个文件,[slug].tsx这就是我们告诉 Next.js 内容应根据url

在此处阅读官方文档getStaticPaths-docs

代码将像这样运行:

  • 首先,该getStaticPaths函数将获取我们博客文章的所有 slug,并告诉 Next.js 我们要为我们返回的每个路径生成静态页面。

  • 其次,该getStaticProps函数将获取我们创建静态页面所需的所有数据。

一旦实现,它在我的代码中看起来像这样:

pages/[slug].tsx

import { NextPage } from"next";
import { createClient } from"@/lib/apollo";import {
  GetBlogPostDocument,
  GetBlogPostQuery,
  GetBlogPostQueryVariables,
  GetBlogPostsDocument,
  GetBlogPostsQuery,
  GetBlogPostsQueryVariables,
  PostDetailed,
} from"@/generated/graphql";type Props = {
  post: PostDetailed;
};const BlogPage: NextPage<Props> = ({ post }) => {
  return (
    <div>
      <main>
        <h1>{post.title}</h1>
        <p>{post.dateAdded}</p>
        {post.tags?.map((tag, index) => (
          <p key={`${tag ?? index}`}>{tag?.__typename}</p>
        ))}
        <p>{post.contentMarkdown}</p>
      </main>
    </div>
  );
};exportdefault BlogPage;type Path = {
  params: {
    slug: string;
  };
};type StaticPaths = {
  paths: { params: { slug: string | null | undefined } }[] | undefined;
  fallback: boolean;
};exportconst getStaticPaths = async (): Promise<StaticPaths> => {
  const client = createClient();const { data } = await client.query<
    GetBlogPostsQuery,
    GetBlogPostsQueryVariables
  >({ query: GetBlogPostsDocument });let paths;
  const posts = data.user?.publication?.posts;if (posts) {
    paths = data.user?.publication?.posts?.map((post) => {
      return {
        params: {
          slug: post?.slug,
        },
      };
    });
  }return {
    paths,
    fallback: false,
  };
};exportconst getStaticProps = async ({ params }: Path) => {
  const client = createClient();const { data } = await client.query<
    GetBlogPostQuery,
    GetBlogPostQueryVariables
  >({
    query: GetBlogPostDocument,
    variables: {
      slug: params.slug,
      hostname: "carlw.hashnode.dev",
    },
  });const post = data.post;return {
    props: {
      post,
    },
  };
};

当访问其中一个页面时,这将产生一个看起来像这样的漂亮汤:(我在开始时说过我不打算进入造型👀

如果您运行 Next.js 构建脚本,您将看到以下输出:

yarn build
yarn run v1.22.15
$ next build
info  - Linting and checking validity of types  
info  - Creating an optimized production build  
info  - Compiled successfully
info  - Collecting page data  
info  - Generating static pages (9/9)
info  - Finalizing page optimization  
Route (pages)                                                                                               Size     First Load JS
┌ ● / (2379 ms)                                                                                             2.75 kB        75.9 kB
├   └ css/ad31fca617ac0c09.css                                                                              1.33 kB
├   /_app                                                                                                   0 B            73.1 kB
├ ● /[slug] (11386 ms)                                                                                      406 B          73.5 kB
├   ├ /react-functional-components-const-vs-function (2450 ms)
├   ├ /react-native-getting-user-device-timezone-and-converting-utc-time-stamps-using-the-offset (2357 ms)
├   ├ /tutorial-write-a-re-useable-react-native-component-and-test-it-with-jest (2263 ms)
├   ├ /helping-developers-find-remote-jobs-since-2019 (1459 ms)
├   ├ /publishupdate-npm-packages-with-github-actions (1429 ms)
├   └ /adding-animations-to-your-react-project-using-lottie (1428 ms)
├ ○ /404                                                                                                    181 B          73.3 kB
└ λ /api/hello                                                                                              0 B            73.1 kB
+ First Load JS shared by all                                                                               73.8 kB
  ├ chunks/framework-2c79e2a64abdb08b.js                                                                    45.2 kB
  ├ chunks/main-f11614d8aa7ee555.js                                                                         26.8 kB
  ├ chunks/pages/_app-891652dd44e1e4e1.js                                                                   296 B
  ├ chunks/webpack-8fa1640cc84ba8fe.js                                                                      750 B
  └ css/876d048b5dab7c28.css                                                                                706 B
λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
✨  Done in 17.63s.

如您所见,它在路由下生成了多个页面[slug],这些页面现在是静态的,将作为您的其他页面托管。

成功🚀

我现在已经演示了如何开始构建您自己的博客或将博客页面添加到现有的 Next.js 项目。使用 Hashnode API 验证类型安全和模式。酷吧!

作为参考,您可以在 GitHub 上查看完整的源代码🤓

https://github.com/ugglr/nextjs-hashnode-graphql-blog

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/350918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Failed at the node-sass@4.14.1 postinstall script

vue项目启动&#xff0c;安装node14.18.0版本&#xff0c;构建时报错&#xff1a; Failed at the node-sass4.14.1 postinstall script 其实在构建过程中&#xff0c;还出现了其他组件的各种报错&#xff0c;最后反思了一下&#xff0c;觉得是nodeJs的版本问题&#xff0c;最…

BIM技巧 | Revit中如何给房间填充颜色?就5步

大家在Revit平面创建好房间后&#xff0c;有没有觉得各房间因为没有着色而区分不明显、视觉效果一般呢&#xff1f; 一、今天就教给大家如何给房间填充上颜色。 01 第一步 首先&#xff0c;将各个房间创建好&#xff1b; 02 第二步 在【建筑】-【房间和面积】单击下拉菜单…

深入理解vue2.x中Object.defineproperty()和vue3.x中Proxy

前言 vue2.x中数据的双向绑定主要通过Object.defineproperty()方法实现&#xff0c;data中的数据改变通过Object.defineProperty()对属性设置set属性&#xff0c;获取通过get属性&#xff0c;Object.defineProperty的作用就是劫持一个对象的属性,通常我们对属性的getter和sett…

魔兽世界私服架设教程——如何搭建魔兽世界私服

TrinityCore是一个魔兽世界服务端模拟器&#xff0c;我们可以通过TrinityCore来学习大型网络游戏服务端的编写&#xff0c;从中汲取营养来编写我们自己的游戏。一、前期准备工作CPU需要支持SSE2指令集Boost版本大于等于1.59.0MySQL数据库版本大于等于5.1.0OpenSSL版本为1.0.xCM…

基于机器学习LSTM的古代汉语切分标注算法及语料库研究 完整代码+数据+论文

完整代码&#xff1a;https://download.csdn.net/download/qq_38735017/87382302摘 要近年来&#xff0c;深度学习的浪潮渗透在科研和生活领域的方方面面&#xff0c;本文主要研究深度学习在自然语言处理&#xff0c;尤其是古汉语自然语言处理方面的应用。本文旨在利用计算机帮…

C#中GDI+的矩形功能扩展

原文出处&#xff1a;https://haigear.blog.csdn.net/article/details/129060020 GDI发展到GDI绘制函数中的参数往往都有矩形这个参数&#xff08;除绘制直线和路径&#xff09;&#xff0c;所以我们用好了矩形绘图就容易多了。 一、中心定位绘制图形 但当我们绘制一个图形时…

Towards Adversarial Attack on Vision-Language Pre-training Models

摘要虽然视觉-语言预训练模型(VLP)在各种视觉-语言(VL)任务上表现出革命性的改进&#xff0c;但关于其对抗鲁棒性的研究在很大程度上仍未被探索。本文研究了常用VLP模型和VL任务的对抗性攻击。首先&#xff0c;我们分析了不同设置下对抗性攻击的性能。通过研究不同扰动对象和攻…

HHDESK图片管理——批量重命名及递归搜索

HHDESK作为一款国产桌面软件&#xff0c;考虑到国人的操作及阅读习惯。因此我们开发了一些有意义的新功能&#xff0c;比如今天要介绍的图片批量重命名及递归搜索功能 1.图片批量重命名功能 网上下载的图片名称大多杂乱无章&#xff0c;一眼望去毫无头绪。 而windows自带的…

第41天|LeetCode198. 打家劫舍、LeetCode213. 打家劫舍II、LeetCode337. 打家劫舍III

1.题目链接&#xff1a;198. 打家劫舍 题目描述&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&a…

Ubuntu中利用aircrack-ng和Wireshark抓空口包

系统&#xff1a;Ubuntu20.04网卡&#xff1a;RTL8188CUS USB网卡工具安装sudo apt-get install aircrack-ngsudo add-apt-repository ppa:wireshark-dev/stable sudo apt update sudo apt install -y wireshark网卡确认网卡是否支持monitor模式&#xff0c;输入iw list命令&am…

Java最全八股文(2023最新整理)

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

vTESTstudio - VT System CAPL Functions - VT2004

纵使生活有白般不顺&#xff0c;我们依然要千般喜乐&#xff0c;万般热爱&#xff0c;只因那些我们喜爱和爱我们的人儿。vtsLoadWFResistance - 从指定文件加载通道的电阻曲线功能&#xff1a;此函数从指定文件加载VT2004通道的电阻曲线注意&#xff1a;该函数不能在任何CAPL处…

电子技术——分立CS和CE放大器的低频响应

电子技术——分立CS和CE放大器的低频响应 我们之前在学习放大器中从来没有关系过信号频率对放大器的影响&#xff0c;也就是说我们默认放大器具有无限的带宽&#xff0c;这当然不符合现实逻辑。为了说明这一点&#xff0c;我们使用下图&#xff1a; 上图描述了MOS或BJT分立电路…

电脑录屏是哪个快捷键?3个录屏快捷键,教你快速录屏

在每天的办公、学习、生活中&#xff0c;很多小伙伴经常需要使用电脑录屏功能。想要快速进行电脑录屏&#xff0c;那么就需要使用电脑录屏的快捷键进行协助。电脑录屏是哪个快捷键&#xff1f;今天&#xff0c;小编就分享3个录屏快捷键&#xff0c;教你如何快速录屏。 一、电脑…

C语言格式化输入和输出; Format格式化

Format格式化 %1s或者%2s,%3s:取字符串的前1,2或者3位。%*c:屏蔽一个字符。%[A-Z]:取一个A到Z的值。 %[^a-z]:不取a到z的值。 %[^\n]&#xff1a;取非换行之前的值。printf("%5d", a):左边补 格式化&#xff1a;有正则在其中。 int main() {printf("%5d\n&quo…

二叉树讲解

对于二叉树&#xff0c;是真正的很难&#xff01;很难&#xff0c;不是一般的难度&#xff01;&#xff01;笔者学习完二叉树&#xff0c;笔记记录了得有三十多页&#xff0c;但是&#xff0c;还是很不理解&#xff08;做题不怎么会&#xff09;下面进入二叉树的基础部分&#…

无法决定博客主题的人必看!如何选择类型和推荐的 5 种选择

是否有人不能迈出第一步&#xff0c;因为博客的类型还没有决定&#xff1f;有些人在出发时应该行动&#xff0c;而不是思考&#xff0c;但让我们冷静下来&#xff0c;仔细想想。博客的难度因流派而异&#xff0c;这在很大程度上决定了随后的发展。因此&#xff0c;在选择博客流…

关于IDM下载器,提示:一个假冒的序列号被用来注册……idea项目文件路径报红

关于IDM下载器&#xff0c;提示&#xff1a;一个假冒的序列号被用来注册……到C:\Windows\System32\drivers\etc 修改目录下面的hosts文件&#xff08;如果没有修改的权限就右键属性hosts文件修改user的权限为完全控制&#xff09;&#xff0c;在hosts里面增加以下内容&#xf…

RadGraph: Extracting Clinical Entities and Relations from Radiology Reports代码

文章来源&#xff1a;NeurIPS 文章类别&#xff1a;IE(Information Extraction) RadGraph主要基于dygie&#xff0c;主要文件为inference.py。 inference.py&#xff1a; 1、get_file_list(data_path) def get_file_list(path):file_list [item for item in glob.glob(f&q…

遮挡贴图(Occlusion Map)和微表面贴图(Microsurface Map)

遮挡贴图&#xff08;Occlusion Map&#xff09; 在3D图形学中&#xff0c;遮挡&#xff08;Occlusion&#xff09;是指光被物体挡住。即便是在PBR中&#xff0c;环境光在某些应该被遮挡的地方&#xff0c;也会以古怪的方式被反射。遮挡贴图&#xff08;Occlusion Map&#xff…