기능 정의
- 검색창에 검색어를 입력하면 `title` 또는 `location`에 검색어가 포함된 데이터를 노출시킨다.
 - 뒤로가기를 눌렀을 때, 이전 검색 결과로 이동한다.
 
컴포넌트 구현
검색창 코드
"뒤로가기를 눌렀을 때, 이전 검색 결과로 이동한다."라는 기능을 구현하기 위해서,
검색어를 입력했을 때 히스토리 스택이 추가되어야 합니다.
따라서 검색어를 검색하면 url 쿼리를 추가하여 히스토리 스택을 쌓아봅시다.
import { Input } from "@nextui-org/input";
import { ChangeEvent, useState } from "react";
interface SearchInputProps {
  onSubmit: (value: string) => void;
}
const SearchInput = ({ onSubmit }: SearchInputProps) => {
  const [value, setValue] = useState("");
  
  return (
    <form
 onSubmit={(e) => 
      {
        e.preventDefault();
        onSubmit(value);
      }}
    >
      <Input
        value={value}
        onChange={(e) =>
        	{ setValue(e.target.value) }
        }
        placeholder="테마명/지점 검색"
        autoComplete="off"
        enterKeyHint="search"
      />
    </form>
  );
};
export default SearchInput;
`form`이 제출되었을 때 화면이 깜빡이는 현상을 막기 위해 `e.preventDefault()`를 실행시킨 후,
props로 받은 `onSubmit`함수에 검색어를 파라미터에 넣고 호출합니다.
<SearchInput onSubmit={(search) => router.push(`?search=${search}`)} />
`SearchInput`의 부모 컴포넌트에서 위와 같이 `prop`을 넘겨줍니다.
`onSubmit` 함수의 파라미터로 전달받은 값을 url 쿼리에 포함시켜 `router.push`합니다.
검색 페이지 코드
import { useRouter, useSearchParams } from "next/navigation";
const searchParmas = useSearchParams();
const search = searchParmas.get("search");
url이 `https://url.com?search=검색어` 일 경우,
`search`에 해당하는 "검색어" 라는 문자열을 가져오기 위해서 위와 같은 코드를 사용할 수 있습니다.
import { useRouter, useSearchParams } from "next/navigation";
// ...
const searchParmas = useSearchParams();
const search = searchParmas.get("search");
useEffect(() => {
    const fetchThemes = async () => {
      setIsLoading(true);
      let url = `api/theme`;
      if (search) url += `?search=${search}`;
      try {
        const response = await fetch(url);
        const { data } = await response.json();
        setThemes(data);
      } finally {
        setIsLoading(false);
      }
    };
}, [search])
url에서 가져온 `search` 값을 사용하여 next API 를 호출하는 코드입니다.
url의 `searchParam`에 `search` 값이 있다면 API url에 `? search=${search}`라는 쿼리를 추가합니다.
`fetch`를 사용하여 API를 호출하고 받아온 `data`를 `themes` state에 저장합니다.
url의 `search` 값이 변경되면 API를 다시 호출하기 위해 `dependency`에 `search`를 추가합니다.
{themes.map((theme) => (
  <ThemeCard key={theme.id} theme={theme} />
))}
`themes` state에 저장한 값을 다음과 같이 노출시키면 완성입니다!
"use client";
import { useRouter, useSearchParams } from "next/navigation";
// ...
import SearchInput from "~/app/themes/components/SearchInput";
const SearchPage = () => {
  const router = useRouter();
  const searchParmas = useSearchParams();
  const search = searchParmas.get("search");
  const [themes, setThemes] = useState<Theme[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  useEffect(() => {
    const fetchThemes = async () => {
      setIsLoading(true);
      let url = `api/theme`;
      if (search) url += `?search=${search}`;
      try {
        const response = await fetch(url);
        const { data } = await response.json();
        setThemes(data);
      } finally {
        setIsLoading(false);
      }
    };
    fetchThemes();
  }, [search]);
  
  if (isLoading) return <></>
  return (
    <>
      <SearchInput onSubmit={(search) => router.push(`?search=${search}`)} />
      <Spacing px={30} />
      <div className="flex pl-[12px]">
        {search ? (
          <Text>"{search}" 검색 결과</Text>
        ) : (
          <Text>전체 검색 결과</Text>
        )}
      </div>
      <Spacing px={16} />
      <ul className="grid grid-cols-2 gap-[8px]">
        {themes.map((theme) => (
          <ThemeCard key={theme.id} theme={theme} />
        ))}
      </ul>
    </>
  );
};
export default SearchPage;
전체 코드는 위와 같습니다.
API 구현
위에서 구현한 `fetchThemes` 함수를 보면 api 엔드포인트는 `api/theme` 입니다.
따라서 `src/app/api/theme/route.ts` 에 있는 `GET` 함수를 만들어봅시다.
// src/app/api/theme/route.ts
import { NextRequest, NextResponse } from "next/server"
export function GET(req: NextRequest) {
  const search = req.nextUrl.searchParams.get('search')
  
  return NextResponse.json({})
}
검색하고자 하는 검색어를 엔드포인트에 `?search=${검색어}`와 같이 쿼리를 붙여주었습니다.
`req.nextUrl.searchParams.get('search')` 를 실행하여 검색어의 값을 가져올 수 있습니다.
const data = await supabase
	.from('theme')
	.select('*')
	.like('title', `%${search}%`)
supabase의 데이터 중 `title`에 검색어를 포함한 데이터를 찾기 위해서 `like` 함수를 사용할 수 있습니다.
const data = await supabase
	.from('theme')
	.select('*')
	.or(`title.like.%${search}%,location.like.%${search}%`)
하지만 기능 정의를 살펴보면 `title` 뿐만 아니라, `location` 컬럼에도 검색어가 포함되어 있는지 함께 검색해야 합니다.
이럴 땐 여러 쿼리를 사용할 수 있는 `or` 함수를 사용합니다.
export async function GET(req: NextRequest) {
  const supabase = createClient();
  let query = supabase.from("theme").select("*");
  const search = req.nextUrl.searchParams.get("search");
  if (search) {
    query = query.or(`title.like.%${search}%,location.like.%${search}%`);
  }
  const { data, error, status } = await query;
  return NextResponse.json({ data, error }, { status });
}
전체 코드입니다.
`search`의 유무에 따라서 `like` 쿼리 실행 여부를 결정했습니다.
supabase 쿼리를 실행한 결과는 `NextResponse`에 `json` 형태로 담아 전달해 줍니다.
결과

검색창에 "포레"라고 검색했을 때, `url`에 `search="포레"` 쿼리가 추가되고,
결과값으로 "포레"를 포함하고 있는 항목이 성공적으로 노출됩니다. 🎉
'개발 > Next' 카테고리의 다른 글
| [Next] react-query QueryClient 올바르게 관리하기 (w. App Router) (0) | 2025.10.30 | 
|---|---|
| [Next] Next 살펴보기 (0) | 2025.10.02 | 
| [Next14] To Do List 만들기 with Supabase (2) Next API Route 사용하여 할 일 목록 구현하기 (2) | 2024.07.08 | 
| [Next14] To Do List 만들기 with Supabase (1) 프로젝트 세팅 (0) | 2024.07.07 | 
| [Next14] 페이지와 레이아웃 : Pages and Layouts (0) | 2024.05.26 |