Implementing Article Search in kbar Command Palette - Modern Next.js Blog Series #27
- Published on
This article is also published at it 邦幫忙 2022 iThome Ironman Contest
This is the 27th article in the "Modern Blog 30 Days" series.
After adding the Command Palette in the previous article, we continue to expand its functionality, enabling article search within the Command Palette and allowing navigation directly to specific article pages!
The final effect is as follows:
The code changes for this article are as follows:
Implementing Article Search Feature in kbar Command Palette
Ideally, to expand the options in kbar, you only need to modify the actions array in CommandPalette.tsx, and importing the list of allPosts provided by Contentlayer should suffice.
However, due to various technical limitations, we need to implement it in a more complicated way. If you find a simpler method, feel free to share it with me!
The technical limitations encountered, related to Next.js, Contentlayer, and kbar, are as follows:
- Contentlayer cannot directly import allPosts in
CommandPalette.tsx
; it can only be imported in Next.js pages' getStaticProps. - Next.js currently does not support writing getStaticProps in
_app.tsx
for site-wide use, so getStaticProps in each page needs to be modified. - Dynamically adding Command Palette options on each page requires using kbar's useRegisterActions.
Thus, our implementation direction is:
- Add
getCommandPalettePosts
in getStaticProps of all pages to retrieve all articles. - Add
useCommandPalettePostActions
in components of all pages to dynamically add all articles to Command Palette options using useRegisterActions. - Modify the CommandPalette component, adding a "Search Articles" action, gathering all article options from 2. for better usability.
Let's start implementing!
Add src/components/CommandPalette/getCommandPalettePosts.ts
:
Copied!import { allPostsNewToOld } from "@/lib/contentLayerAdapter"; export type PostForCommandPalette = { slug: string; title: string; path: string; }; export const getCommandPalettePosts = (): PostForCommandPalette[] => { const commandPalettePosts = allPostsNewToOld.map((post) => ({ slug: post.slug, title: post.title, path: post.path, })); return commandPalettePosts; };
Add src/components/CommandPalette/useCommandPalettePostActions.tsx
:
Copied!import { useRegisterActions } from "kbar"; import { useRouter } from "next/router"; import { PostForCommandPalette } from "./getCommandPalettePosts"; export const useCommandPalettePostActions = ( posts: PostForCommandPalette[] ): void => { const router = useRouter(); useRegisterActions( posts.map((post) => ({ id: post.slug, name: post.title, perform: () => router.push(post.path), section: "Search Articles", parent: "search-posts", })), [] ); };
Next, modify each Next.js page to include the logic of getCommandPalettePosts
and useCommandPalettePostActions
.
Modify src/pages/index.tsx
:
Copied!// ... import { getCommandPalettePosts, PostForCommandPalette, } from "@/components/CommandPalette/getCommandPalettePosts"; import { useCommandPalettePostActions } from "@/components/CommandPalette/useCommandPalettePostActions"; // ... type Props = { posts: PostForIndexPage[]; commandPalettePosts: PostForCommandPalette[]; }; export const getStaticProps: GetStaticProps<Props> = () => { const commandPalettePosts = getCommandPalettePosts(); // ... return { props: { posts, commandPalettePosts } }; }; const Home: NextPage<Props> = ({ posts, commandPalettePosts }) => { useCommandPalettePostActions(commandPalettePosts); // ... }; // ...
Modify src/pages/posts/[slug].tsx
:
Copied!// ... import { getCommandPalettePosts, PostForCommandPalette, } from "@/components/CommandPalette/getCommandPalettePosts"; import { useCommandPalettePostActions } from "@/components/CommandPalette/useCommandPalettePostActions"; // ... type Props = { post: PostForPostPage; prevPost: RelatedPostForPostLayout; nextPost: RelatedPostForPostLayout; commandPalettePosts: PostForCommandPalette[]; }; // ... export const getStaticProps: GetStaticProps<Props> = ({ params }) => { const commandPalettePosts = getCommandPalettePosts(); // ... return { props: { post, prevPost, nextPost, commandPalettePosts, }, }; }; const PostPage: NextPage<Props> = ({ post, prevPost, nextPost, commandPalettePosts, }) => { useCommandPalettePostActions(commandPalettePosts); // ... }; // ...
Finally, modify src/components/CommandPalette/CommandPalette.tsx
to add the Search section in the actions array:
Copied!import { MagnifyingGlassIcon, // ... } from "@heroicons/react/24/outline"; // ... export default function CommandPalette({ children }: Props) { // ... const actions = [ // Page section // ... // Add this section // Search section // - Search posts { id: "search-posts", name: "Articles", keywords: "search find posts writing words blog articles thoughts 搜尋 尋找 文章 寫作 部落格", icon: <MagnifyingGlassIcon className="h-6 w-6" />, section: "Search", }, // Operation section // - Theme toggle // ... ]; return ( <KBarProvider actions={actions}> <CommandBar /> {children} </KBarProvider> ); } // ...
Results
That's it! Use pnpm dev
, enter the website, press Ctrl + K
(Windows) or Cmd + K
(Mac), or click the Command icon in the top-right corner to open the Command Palette. You'll see the "Search Articles" operation available for execution!
The final effect is as follows:
The code changes for this article are as follows:
References
- timc1/kbar: fast, portable, and extensible cmd+k interface for your site
- kbar - command+k interface for your site
Next Article
Congratulations on successfully adding article search functionality to the Command Palette!
In the next article, we finally complete the last key feature of this series: "i18next multilingual support"!