内部 API 使用 getServerSideProps 获取? (Next.js)

Internal API fetch with getServerSideProps? (Next.js)

我是 Next.js 的新手,我正在尝试理解建议的结构并处理页面或组件之间的数据。

例如,在我的页面 home.js 中,我获取了一个名为 /api/user.js 的内部 API,其中 returns 来自 MongoDB 的一些用户数据。我这样做是通过使用 fetch()getServerSideProps() 中调用 API 路由,它在一些计算后将各种道具传递给页面。

根据我的理解,这对 SEO 有好处,因为 props 得到 fetched/modified 服务器端并且页面使它们准备好呈现。但是后来我在 Next.js 文档中读到你不应该对 getServerSideProps() 中的所有 API 路由使用 fetch()。那么我应该怎么做才能遵守良好的实践和良好的 SEO?

我没有在 API 路线中对 home.js 进行所需计算的原因是我需要来自这条 API 路线的更多通用数据,因为我将使用它也在其他页面中。

我还必须考虑缓存,客户端使用 SWR 获取内部非常简单API,但服务器端我还不确定如何实现它。

home.js:

export default function Page({ prop1, prop2, prop3 }) {
        // render etc.
}

export async function getServerSideProps(context) {
  const session = await getSession(context)
  let data = null
  var aArray = [], bArray = [], cArray = []
  const { db } = await connectToDatabase()

  function shuffle(array) {
    var currentIndex = array.length, temporaryValue, randomIndex;
    while (0 !== currentIndex) {
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }
    return array;
  }

  if (session) {
    const hostname = process.env.NEXT_PUBLIC_SITE_URL
    const options = { headers: { cookie: context.req.headers.cookie } }
    const res = await fetch(`${hostname}/api/user`, options)
    const json = await res.json()
    if (json.data) { data = json.data }

    // do some math with data ...
    // connect to MongoDB and do some comparisons, etc.

But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().

您想直接在 getServerSideProps 中使用 API 路由中的逻辑,而不是调用内部 API。这是因为 getServerSideProps 就像 API 路由一样在服务器上运行(从服务器向服务器本身发出请求是没有意义的)。您可以从文件系统读取或直接从 getServerSideProps.

访问数据库

来自 Next.js getServerSideProps 文档:

It can be tempting to reach for an API Route when you want to fetch data from the server, then call that API route from getServerSideProps. This is an unnecessary and inefficient approach, as it will cause an extra request to be made due to both getServerSideProps and API Routes running on the server.

(...) Instead, directly import the logic used inside your API Route into getServerSideProps. This could mean calling a CMS, database, or other API directly from inside getServerSideProps.

(注意是


这是一个小的重构示例,它允许您在 getServerSideProps 中重用 API 路由中的逻辑。

假设您有这条简单的 API 路线。

// pages/api/user
export default async function handler(req, res) {
    // Using a fetch here but could be any async operation to an external source
    const response = await fetch(/* external API endpoint */)
    const jsonData = await response.json()
    res.status(200).json(jsonData)
}

您可以将抓取逻辑提取到一个单独的函数中(如果需要,仍然可以将其保留在 api/user 中),这在 API 路由中仍然可用。

// pages/api/user
export async function getData() {
    const response = await fetch(/* external API endpoint */)
    const jsonData = await response.json()
    return jsonData
}

export default async function handler(req, res) {
    const jsonData = await getData()
    res.status(200).json(jsonData)
}

而且还允许您在 getServerSideProps.

中重复使用 getData 函数
// pages/home
import { getData } from './api/user'

//...

export async function getServerSideProps(context) {
    const jsonData = await getData()
    //...
}

只需尝试使用 useSWR,示例如下

import useSWR from 'swr'
import React from 'react';

//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());

const Categories = () => {
 //getting data and error
 const { data, error } = useSWR('/api/category/getCategories', fetcher)
 if (error) return <div>Failed to load</div>
 if (!data) return <div>Loading...</div>
 if (data){
   // {data} is completed, it's ok!
   //your code here to make something with {data}
   return (
      <div>
      //something here, example {data.name}
      </div>
   )
 }
}

export default Categories

请注意,fetchsupports absolute URLs,这就是我不喜欢使用它的原因。

P.S。根据 docs,您甚至可以将 useSWRSSR 一起使用。

You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps

我承认,你说的是对的,但问题依然存在。假设您已经编写了后端并且您的 api 是安全的,那么从安全和编写的后端中提取逻辑似乎很烦人并且浪费时间和精力。另一个缺点是,通过从后端提取逻辑,您必须重写自己的代码来处理错误并验证用户的身份并验证您编写的后端中存在的用户请求。我想知道是否可以在 nextjs 中调用 api 而无需从 middlewars 中提取逻辑?答案是肯定的,这是我的解决方案: npm i node-mocks-http

import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
  return (
    <section>
      <h2>Latest News</h2>
      <NewsCard news={news} />
    </section>
  );
}
export async function getServerSideProps() {
  let req = httpMocks.createRequest();
  let res = httpMocks.createResponse();
  async function callMids(req, res, index, ...mids) {
    index = index || 0;
    if (index <= mids.length - 1)
      await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
  }
  await callMids(
    req,
    res,
    null,
    dbConnectMid,
    logger,
    newsController.sendAllNews
  );
  return {
    props: { news: res._getJSONData() },
  };
}

重要提示: 如果您在所有中间件中使用我的代码,请不要忘记使用 await next() 而不是 next() 否则您会得到一个错误。 另一个解决方案:next connect 有 run 方法,可以做类似 mycode 的事情,但我个人有一些问题;这是它的 link: next connet run method to call next api's in serverSideProps