You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js应用集成搜索功能,Zustand传参到搜索结果页异常问题求助

错误点汇总
  • getStaticProps 是Next.js服务端执行的生命周期函数,运行阶段完全无法访问客户端的Zustand状态,你在其中调用useStore获取关键词的逻辑根本无法执行。
  • Zustand store直接定义在SearchBar.js内且未导出,SearchResults.js无法导入访问store内的状态。
  • 搜索跳转时未携带关键词参数,就算不使用服务端渲染,刷新搜索结果页后状态也会丢失,也无法支持搜索页链接分享。
  • SearchResults.js存在依赖缺失:未导入useState、styles、Footer、Image等依赖,直接运行会报错。
  • 视频过滤逻辑无效:.filter((vid) => vid.snippet.title.toLowerCase())没有实际过滤规则,非空字符串永远返回真值,相当于没有执行过滤。
正确实现方案

这里推荐优先使用URL查询参数传递关键词,既支持页面刷新、链接分享,也能规避服务端生命周期拿不到客户端状态的问题。

步骤1:调整路由跳转逻辑,携带搜索参数

修改SearchBar.js的点击跳转逻辑,不需要依赖Zustand也能实现:

// SearchBar.js
import { useRouter } from 'next/router';
import Image from 'next/image';
import { useState } from 'react';
import styles from '../styles/Header.module.scss';
import loupe from '../public/images/loupe.png';

const SearchBar = () => {
  const router = useRouter();
  const [keyword, setKeyword] = useState('');

  const handleClick = (e) => {
    e.preventDefault();
    if (!keyword.trim()) return;
    // 跳转时携带搜索参数
    router.push(`/searchResults?q=${encodeURIComponent(keyword.trim())}`);
  };

  return (
    <div className={styles.searchBar}>
      <input
        type='text'
        value={keyword}
        placeholder='Suche...'
        onChange={(e) => setKeyword(e.target.value)}
      />
      <button className={styles.searchBtn} type='submit' onClick={handleClick}>
        <Image src={loupe} alt='' />
      </button>
    </div>
  );
};

export default SearchBar;

如果需要全局存储搜索关键词,可单独抽离Zustand store,避免重复定义:

// store/useSearchStore.js
import create from 'zustand';

const useSearchStore = create((set) => ({
  keyword: '',
  setKeyword: (keyword) => set({ keyword }),
}));

export default useSearchStore;

在需要使用状态的组件导入即可,注意不要在getStaticProps/getServerSideProps这类服务端生命周期中调用。

步骤2:修改SearchResults.js,读取URL参数请求数据

使用getServerSideProps在服务端读取URL参数请求接口,也可以根据需求换成客户端用SWR/React Query请求:

// SearchResults.js
import { fetchData } from '../lib/utils';
import { useState } from 'react';
import Image from 'next/image';
import Moment from 'react-moment';
import { Modal } from 'react-responsive-modal';
import ReactPlayer from 'react-player/youtube';
import Footer from '../components/Footer'; // 按实际文件路径调整
import styles from '../styles/SearchResults.module.scss'; // 按实际文件路径调整

export default function SearchResults({ videos, keyword }) {
  const [modalIsOpen, setModalIsOpen] = useState(false);
  const [modalData, setModalData] = useState(null);

  const videoURL = modalData ? `https://www.youtube.com/watch?v=${modalData}` : '';

  // 修正排序和过滤逻辑
  const sortedVids = videos
    .filter(vid => vid.snippet.title !== 'Private video')
    .sort((a, b) => new Date(b.snippet.videoPublishedAt) - new Date(a.snippet.videoPublishedAt))
    // 如需前端过滤标题包含关键词的内容可以取消注释下一行
    // .filter(vid => vid.snippet.title.toLowerCase().includes(keyword.toLowerCase()))

  return (
    <>
      <div className={`${styles.playlist_container} ${styles.search}`}>
        <div className={styles.main_container}>
            <h1>Search results for: {keyword}</h1>
          {sortedVids.map((vid, id) => {
            return (
              <div className={styles.item_container} key={id}>
                <div className={styles.clip_container}>
                  <Image
                    className={styles.thumbnails}
                    src={vid.snippet.thumbnails.medium.url}
                    layout='fill'
                    objectFit='cover'
                    alt={vid.snippet.title}
                  />
                  <button
                    className={styles.playBtn}
                    onClick={() => {
                      setModalData(vid.snippet.resourceId.videoId);
                      setModalIsOpen(true);
                    }}
                  >
                    <Image src='/images/play.svg' width='60' height='60' alt='播放' />
                  </button>
                </div>
                <div className={styles.details_container}>
                  <h3>{vid.snippet.title}</h3>
                </div>
              </div>
            );
          })}
        </div>
      </div>
      <div>
        <Modal
          open={modalIsOpen}
          onClose={() => setModalIsOpen(false)}
          center
          classNames={{
            overlay: 'customOverlay',
            modal: 'customModal',
            overlayAnimationIn: 'customEnterOverlayAnimation',
            overlayAnimationOut: 'customLeaveOverlayAnimation',
            modalAnimationIn: 'customEnterModalAnimation',
            modalAnimationOut: 'customLeaveModalAnimation',
          }}
          animationDuration={800}
        >
          {videoURL && <ReactPlayer
            playing={true}
            url={videoURL}
            width='100%'
            height='calc(100vh - 100px)'
            config={{
              youtube: {
                playerVars: {
                  autoplay: 1,
                  controls: 1,
                },
              },
            }}
          />}
        </Modal>
      </div>
      <Footer />
    </>
  );
}

export async function getServerSideProps(context) {
  // 从URL参数读取关键词
  const keyword = context.query.q || '';
  if (!keyword) {
    return {
      props: {
        videos: [],
        keyword: ''
      }
    }
  }

  const { YOUTUBE_KEY } = process.env;
  const uploadsURL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${encodeURIComponent(keyword)}`;

  const uploadsData = await fetchData(uploadsURL);
  return {
    props: {
      videos: uploadsData.items || [],
      keyword
    },
  };
}

内容的提问来源于stack exchange,提问作者Ydonut13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 10:57:03