Часть 1: Редизайн моего сайта с помощью t3-стека


Краткое введение

Это серия статей блога, в которых подробно описывается, как я настраиваю свой сайт с помощью стека t3. Если вы не читали предыдущие статьи, рекомендую посмотреть

  • Часть 1 : Редизайн моего сайта с помощью t3-stack
  • Часть 2 : Отображение отдельных статей
  • Часть 3 : Добавление оглавления к статьям нашего блога
  • Часть 4 : Приведение в порядок конечного продукта

Вы можете посмотреть сайт здесь

Начальная установка

Недавно я наткнулся на стек t3, просматривая twitter, и решил поработать над его внедрением, создав новое приложение t3 с помощью пакета create-t3-app npm. Однако, прежде чем я смог многое сделать, я обнаружил эту специфическую ошибку при сборке приложения на Vercel.

Немного покопавшись в репозитории, который создал create-t3-app, я отследил ее до определенного файла, а именно ./src/env/schema.mjs, который содержал строку кода в виде

// @ts-check
import { z } from "zod";

/**
 * Specify your server-side environment variables schema here.
 * This way you can ensure the app isn't built with invalid env vars.
 */
export const serverSchema = z.object({
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(["development", "test", "production"]),
  NEXTAUTH_SECRET: z.string(),
   NEXTAUTH_URL: z.string().url(),
  DISCORD_CLIENT_ID: z.string(),
  DISCORD_CLIENT_SECRET: z.string(),
});
...

Войти в полноэкранный режим Выйти из полноэкранного режима

Затем она вызывалась в client.mjs и server.mjs в процессе сборки, что приводило к возникновению ошибки. Исправление описано ниже.

export const serverSchema = z.object({
  // DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(["development", "test", "production"]),
  // NEXTAUTH_SECRET: z.string(),
  // NEXTAUTH_URL: z.string().url(),
  // DISCORD_CLIENT_ID: z.string(),
  // DISCORD_CLIENT_SECRET: z.string(),
});

Войдите в полноэкранный режим Выйдите из полноэкранного режима

Давайте начнем!

Вопросы Github как CMS

Для этой части мы будем использовать официальный npm-пакет Github octokit, поэтому выполните следующую команду

npm install @octokit/graphql
Войдите в полноэкранный режим Выйти из полноэкранного режима

прежде чем приступить к остальной части этого раздела

Настройка персонального маркера доступа

Примечание: Персональный маркер доступа используется Github для получения доступа к информации о ваших репозиториях и коде. Он понадобится вам для этого конкретного проекта, чтобы использовать Github Issues в качестве CMS.

Сначала зайдите на сайт github.com, перейдите к своим настройкам и нажмите на Developer Settings.

Затем вам нужно будет нажать на Personal Access Tokens

Наконец, вам нужно настроить токен доступа так, чтобы он имел доступ только к public_repo, как показано на рисунке.

Затем вам следует обновить этот токен в локальном файле .env.

Не забудьте перезапустить ваш сервер разработки, чтобы новый .env файл загрузился.

Настройка маршрутизатора TRPC

Если вы использовали create-t3-app для настройки проекта, все должно быть готово. Давайте попробуем в этом случае сделать github-спецификацию. Перейдите в src/server/router/index.ts и вы найдете следующий код

// src/server/router/index.ts
import { createRouter } from "./context";
import superjson from "superjson";

import { exampleRouter } from "./example";
import { protectedExampleRouter } from "./protected-example-router";

export const appRouter = createRouter()
  .transformer(superjson)
  .merge("example.", exampleRouter)
  .merge("question.", protectedExampleRouter);

// export type definition of API
export type AppRouter = typeof appRouter;
Вход в полноэкранный режим Выйти из полноэкранного режима

Если вы когда-нибудь раньше использовали express, вы поймете, что TRPC работает очень похоже. Мы можем обеспечить модульное разделение отдельных маршрутов, просто работая с функцией .merge. Давайте создадим новый маршрутизатор для обработки наших проблем на github, как показано ниже

import { createRouter } from "./context";
import { z } from "zod";

export const githubRouter = createRouter().query("hello", {
  input: z
    .object({
      text: z.string(),
    })
    .nullish(),
  resolve({ input }) {
    const { text } = input;
    return {
      greeting: `Hello from github router. You indicated ${text}`,
    };
  },
});
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем прикрепить его к нашему основному appRouter в файле index.ts, как показано ниже

// src/server/router/index.ts
import { createRouter } from "./context";
import superjson from "superjson";

import { githubRouter } from "./github-router";

export const appRouter = createRouter()
  .transformer(superjson)
  .merge("github.", githubRouter);

// export type definition of API
export type AppRouter = typeof appRouter;
Войти в полноэкранный режим Выйти из полноэкранного режима

и вызвать его на фронтенде, изменив index.tsx следующим образом

import type { NextPage } from "next";
import Head from "next/head";
import { trpc } from "../utils/trpc";

type TechnologyCardProps = {
  name: string;
  description: string;
  documentation: string;
};

const Home: NextPage = () => {
  const hello = trpc.useQuery(["github.hello", { text: "from tRPC" }]);
  console.log(hello?.data?.greeting);

  return <>Welcome to Tailwind</>;
};

export default Home;
Войти в полноэкранный режим Выйти из полноэкранного режима

Это даст нам следующий вывод в консоли, указывающий на то, что мы успешно связали фронтенд и бэкенд.

Причина, по которой у нас четыре разных консольных журнала, заключается в том, что create-t3-app автоматически включает SSR для нас, поэтому содержимое автоматически отображается на сервере перед отправкой на фронтенд, таким образом, код фронтенда выполняется дважды.

Получение списка постов

Давайте сначала получим наш список выпусков с github. Это можно сделать с помощью следующего фрагмента кода

import { graphql } from "@octokit/graphql";

const graphqlWithAuth = graphql.defaults({
  headers: {
    authorization: `token ${process.env.GITHUB_TOKEN}`,
  },
});

export const getPosts = async () => {
  const { repository } = await graphqlWithAuth(`
        {
          repository(owner: <your github username>, name: <the repository you're importing the issues from>) {
            issues(last: 50) {
              edges {
                node {
                    title
                    createdAt
                    labels(first: 3) {
                        nodes{
                            name
                        }
                    }
                }
              }
            }
          }
        }
      `);

  console.log(repository);

  return repository;
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание: Более подробную информацию об API вы можете найти в документации github.

В моем конкретном списке проблем у меня есть два тега draft и published. Я хочу получить список только тех проблем, которые помечены тегом published. Мы можем сделать это, выполнив фильтр над возвращаемым объектом, который мы получаем

export const getPublishedPosts: () => Promise<githubPostTitle[]> = async () => {
  const { repository } = await graphqlWithAuth(`
        {
          repository(owner: <your github username> , name: <your repository where the issues are in>) {

            issues(last: 50) {
              edges {
                node {
                    title
                    createdAt
                    labels(first: 3) {
                        nodes{
                            name
                        }
                    }
                }
              }
            }
          }
        }
      `);

  return repository.issues.edges
    .map(({ node }) => {
      return { ...node };
    })
    .filter((post: githubPostTitle) => {
      return post.labels.nodes[0].name === "published";
    });
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы также добавляем определенный нами тип githubPostTitle в код, как показано ниже

type githubPostStatus = "draft" | "published";

type githubPostTitle = {
  createdAt: string;
  title: string;
  labels: {
    nodes: [{ name: githubPostStatus }];
  };
};
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь все, что нам нужно сделать, это вызвать это в нашем файле github-router.ts, который хранит наш маршрутизатор github, и у нас есть маршрут, который мы можем вызвать из нашего фронтенда, который отображает наш список постов, как показано ниже.

*Примечание: * Маршрутизатор в данном случае — это просто набор запросов и мутаций, которые мы определяем. Каждый запрос и мутация имеют соответствующий метод resolve для получения определенного результата. Мы можем обеспечить безопасность типов API, используя zod, который имеет ряд функций-оберток для использования.

import { createRouter } from "./context";
import { z } from "zod";
import { getPublishedPosts, githubPostTitle } from "../../utils/github";

export const githubRouter = createRouter().query("get-posts", {
  async resolve({ input }) {
    const repositoryInformation: githubPostTitle[] = await getPublishedPosts();
    return {
      posts: repositoryInformation,
    };
  },
});

Вход в полноэкранный режим Выход из полноэкранного режима

Рендеринг наших постов на фронтенде

Теперь мы можем вызвать наш конкретный маршрут с помощью пользовательского хука react useQuery.

const posts = trpc.useQuery(["github.get-posts"]);
Вход в полноэкранный режим Выйти из полноэкранного режима

Это, в свою очередь, возвращает объекты, которые выглядят следующим образом

posts : [
    {
        "title": "Setting up a development enviroment",
        "createdAt": "2022-07-27T06:14:56Z",
        "labels": {
            "nodes": [
                {
                    "name": "published"
                }
            ]
        }
    },
    ...
]
Войти в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что как только мы добавили фильтр, все наши вопросы, помеченные как draft, исчезли. Затем мы можем отобразить этот список постов на экране, чтобы отобразить их в полноэкранном режиме

import type { NextPage } from "next";
import Head from "next/head";
import { trpc } from "../utils/trpc";

const Home: NextPage = () => {
  const posts = trpc.useQuery(["github.get-posts"]);

  return (
    <>
      <h1>Posts</h1>
      {posts.data?.posts?.map((item) => {
        return <p key={item.title}>{item.title}</p>;
      })}
    </>
  );
};

export default Home;
Войти в полноэкранный режим Выйти из полноэкранного режима

что в свою очередь дает

Вуаля! Теперь мы подключили наши github issues к фронтенду NextJS с помощью TRPC.

В следующей части этой серии мы перейдем к написанию пользовательской страницы, которая сможет правильно отображать уценку на каждой конкретной странице.

Я опубликовал следующую часть этой серии, вы можете прочитать ее здесь

Оцените статью
devanswers.ru
Добавить комментарий