文档
教程
构建 Next.js 应用

入门

在本演练中,我们将开始使用 Next.js 构建搜索体验。

您不需要使用 Next.js 才能使用 Searchkit,但这是最简单的入门方法。

在本演练中,我们将

  • 设置一个 api 路由以从 Elasticsearch 获取结果
  • 使用 React InstantSearch 显示结果 🎉

下载示例项目

您可以在此处查看带有 Searchkit 的 Next.js 项目

curl https://codeload.github.com/searchkit/searchkit/tar.gz/main | \
tar -xz --strip=2 searchkit-main/examples/with-ui-nextjs-react

或在 github 上查看示例代码库 此处 (在新标签页中打开)

Code Sandbox 示例

您也可以在此处查看代码沙箱示例

创建 Next.js 应用

ℹ️

本教程将使用新的 Next.js App Router。如果您使用的是 pages,请在遵循本教程时牢记这一点。

首先,我们需要创建一个 Next.js 应用。我们可以通过运行以下命令来实现

npx create-next-app@latest

并按照说明操作。

导航到新创建的目录。

安装依赖项

接下来,我们需要为该项目安装依赖项

  npm install @searchkit/instantsearch-client @searchkit/api react-instantsearch

设置 Node API

app/api/search 目录中创建一个名为 route.ts 的新文件,并添加以下代码

app/api/search/route.ts
import Client from "@searchkit/api";
import { NextRequest, NextResponse } from 'next/server'
 
const apiConfig = {
  connection: {
    host: "<replace-with-your-elasticsearch-host>",
    // if you are authenticating with an api key
    // https://searchkit.elastic.ac.cn/docs/guides/setup-elasticsearch#connecting-with-api-key
    // apiKey: '###'
    // if you are authenticating with a username/password combo
    // https://searchkit.elastic.ac.cn/docs/guides/setup-elasticsearch#connecting-with-usernamepassword
    // auth: {
    //   username: "elastic",
    //   password: "changeme"
    // },
  },
  search_settings: {
    highlight_attributes: ["title", "actors"],
    search_attributes: ["title", "actors"],
    result_attributes: ["title", "actors"],
    facet_attributes: ["type", "rated"],
  },
};
 
const apiClient = Client(apiConfig);
 
export async function POST(req: NextRequest, res: NextResponse) {
  const data = await req.json()
 
  const results = await apiClient.handleRequest(data)
  return NextResponse.json(results)
}

hostapiKey 替换为您自己的 Elasticsearch 主机和 API 密钥。apiKey 是可选的,但在生产环境中建议使用。您可以在 此处 (在新标签页中打开) 找到有关 API 密钥的更多信息。

这将在 /api/search 路径下设置一个新的 Next.js 路由处理程序 (在新标签页中打开)。此路由将处理搜索请求并使用 InstantSearch Elasticsearch 适配器来处理请求。然后将响应返回给客户端。

有关 API 配置的更多信息,请参阅 API 配置 文档。

设置前端

现在我们已经设置了 API,我们可以开始构建前端了。我们将使用 react-instantsearch (在新标签页中打开) 来构建搜索体验。

首先,我们需要在 app 目录中创建一个新文件(如果不存在),名为 page.tsx,并添加以下代码

app/page.tsx
import { InstantSearch, SearchBox, Hits } from "react-instantsearch";
import createClient from "@searchkit/instantsearch-client";
 
const searchClient = createClient({
  url: "/api/search",
});
 
export default function Search() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName="<elasticsearch index or alias name>"
    >
      <SearchBox />
      <Hits />
    </InstantSearch>
  );
}

Instantsearch 将使用 searchClient 向我们之前创建的 API 发出请求。 indexName 是我们要搜索的索引的名称。

运行应用

现在我们已经设置好了一切,我们可以运行该应用并查看搜索体验。

npm run dev

IMAGE1

可搜索属性

现在我们已经设置了搜索体验,我们可以添加其他搜索功能了。

调整搜索字段

我们可以通过更新search_attributesapiConfig 对象中的 app/api/search/route.ts 文件来调整搜索字段。

  search_attributes: ["title^3", "actors", "plot"],

上面我们将标题的权重提升了 3 倍。这意味着标题将比其他字段具有更高的权重。这将确保标题在搜索结果中具有更高的重要性。

覆盖默认查询

我们可以选择通过在 handleRequest 方法(在 app/api/search/route.ts 文件中调用)中实现 getQuery 函数来覆盖默认搜索查询。

此函数将接收查询,并返回将用于搜索索引的 Elasticsearch 查询。

const results = await apiClient.handleRequest(body, {
  getQuery: (query, search_attributes) => {
    return [
      {
        combined_fields: {
          query,
          fields: search_attributes,
        },
      },
    ];
  },
});

自定义结果命中

我们可以添加一个自定义命中组件来显示结果。我们可以在 components 目录中创建一个名为 Hit.ts 的新文件,并添加以下代码

下面我们使用来自 react-instantsearchHighlight 组件来突出显示标题和演员字段中的搜索词。

components/Hit.tsx
import { Highlight } from "react-instantsearch";
 
const hitView = (props) => {
  return (
    <div>
      <h2>
        <Highlight hit={props.hit} attribute="title" />
      </h2>
      <br />
 
      <Highlight hit={props.hit} attribute="actors" />
    </div>
  );
};

我们需要将 attribute 属性传递给 highlight_attributes 配置,以告知要为哪些字段提供突出显示选项。

app/api/search/routes.ts
  highlight_attributes: ["title", "actors"],

然后,我们可以在 app/page.tsx 文件中导入 Hit 组件,并将其传递给父 Hits 组件。

import Hit from "../components/Hit";
 
export default function Search() {
  return (
    <InstantSearch searchClient={searchClient} indexName="movies">
      <SearchBox />
      <Hits hitComponent={Hit} />
    </InstantSearch>
  );
}

分面

添加细化列表分面

首先更新 apiConfig 对象(在 app/api/search/route.ts 文件中),以添加 type 分面。

app/api/search/route.ts
  facet_attributes: [{ attribute: "type", "type": "string" }],

这假设索引中存在一个 type 字段,该字段是 keyword 类型字段。

如果该字段是 text 类型字段,则可以定义并使用 type.keyword 子字段。

  facet_attributes: [{ attribute: "type", field: "type.keyword", type: "string" }],

然后,我们可以将 RefinementList 组件添加到 pages/search.js 文件中。

import {
  InstantSearch,
  SearchBox,
  Hits,
  RefinementList,
} from "react-instantsearch";
 
export default function Search() {
  return (
    <InstantSearch searchClient={searchClient} indexName="movies">
      <SearchBox />
      <RefinementList attribute="type" />
      <Hits hitComponent={Hit} />
    </InstantSearch>
  );
}

使其可搜索

默认情况下,RefinementList 组件将显示分面中的所有值。我们可以通过添加 searchable 属性使其可搜索。

<RefinementList attribute="type" searchable />

添加基于数字范围的分面

首先更新 apiConfig 对象(在 app/page.tsx 文件中),以添加 imdbrating 分面。这需要 imdbrating 字段在 Elasticsearch 索引中为数字类型字段,例如 float

facet_attributes: [
  { attribute: "imdbrating", type: "numeric" },
  { attribute: "type", field: "type.keyword", type: "string" }
],

然后,我们可以将 RangeInput 组件添加到 app/page.tsx 文件中。

import {
  InstantSearch,
  SearchBox,
  Hits,
  RangeInput,
} from "react-instantsearch";
 
<RangeInput
  attribute="imdbrating"
/>;

服务器端渲染

下面我们添加以下额外的导入

  1. 来自 react-instantsearchgetServerState 函数
  2. 来自 react-dom/serverrenderToString 函数
  3. 来自 react-instantsearchInstantSearchServerStateInstantSearchSSRProvider 组件
  4. 来自 react-instantsearch-router-nextjscreateInstantSearchRouterNext 函数
  5. 来自 next/routersingletonRouter

然后,我们将 InstantSearch 组件包装在 InstantSearchSSRProvider 组件中,并将 serverState 属性传递给它。

这使我们能够在服务器上渲染搜索体验并将初始状态发送到客户端。这将使搜索体验加载速度更快,并提高 SEO。

import { 
  InstantSearch, SearchBox, Hits, RefinementList, RangeInput, 
  InstantSearchServerState, InstantSearchSSRProvider, getServerState
} from 'react-instantsearch';
import { renderToString } from 'react-dom/server';
 
import Client from '@searchkit/instantsearch-client'
import { GetServerSideProps } from 'next';
import { createInstantSearchRouterNext } from 'react-instantsearch-router-nextjs';
import singletonRouter from 'next/router';
 
type WebProps = {
  serverState?: InstantSearchServerState;
  url?: string;
  serverUrl?: string;
};
 
export default function Web({ serverState, url, serverUrl }: WebProps) {
 
    const searchClient = Client({
      url: serverUrl + '/api/product-search',
    });
 
    return (
      <InstantSearchSSRProvider {...serverState}>
 
        <div className="ais-InstantSearch">
 
          <InstantSearch searchClient={searchClient} indexName="movies">
            <SearchBox />
            <RefinementList attribute="type" searchable />
            <RangeInput attribute="imdbrating" />
            <Hits hitComponent={Hit} />
          </InstantSearch>
 
        </div>
      </InstantSearchSSRProvider>
    );
}
 
export const getServerSideProps: GetServerSideProps<WebProps> =
  async function getServerSideProps({ req }) {
    const protocol = req.headers.referer?.split('://')[0] || 'http';
    const serverUrl = `${protocol}://${req.headers.host}`;
    const url = `${protocol}://${req.headers.host}${req.url}`;
    const serverState = await getServerState(<Web url={url} serverUrl={serverUrl} />, {
      renderToString,
    });
 
    return {
      props: {
        serverState,
        url,
        serverUrl
      },
    };
  };

总结

我们使用 Elasticsearch 和 Algolia InstantSearch 从头开始快速构建了一个非常不错的搜索体验。我们还学习了如何通过调整搜索字段和覆盖默认 Elasticsearch 查询来自定义搜索体验。


Apache 2.0 2024 © Joseph McElroy。
需要帮助?加入 discord