๐ Algolia ๊ฒ์์ NextJS ๋ฐ ๋งํฌ๋ค์ด ํ์ผ๊ณผ ์ด๋ป๊ฒ ํตํฉํ์ต๋๊น?
์ฒ์์๋ GitBook ์ํํธ์จ์ด๊ฐ ๊ทธ ๋น์ ๋น๋๊ณ ํธ๋ ๋ํ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ๊ธฐ ์์ํ์ต๋๋ค. ์๋ก์ด ๊ฒ์ ์์ํ ๋ ๋ชจ๋ ๋จ๊ณ์์ ์ฒ์๋ถํฐ ์์ํ๊ณ ์ถ์ง ์๊ธฐ ๋๋ฌธ์ ์ข์ ์กฐ์น์์ต๋๋ค. ์ฃผ์ ๋ชฉํ๋ ๊ณ ๊ฐ์๊ฒ ์ข์ ํ์ง์ ์ฝ๋์ ๊ฐ์น๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค. ์๊ฐ์ ์ ์ฝํ๊ธฐ ์ํด ๊ตฌ๋งคํ ์ ์๋(๋๋ ๊ตฌ๋งคํด์ผ ํ๋) ๋ค๋ฅธ ๋ชจ๋ ๊ฒ์ ๋๋ค.
๊ฐ๋จํ ๋งํด ๋ช ๋ฌ ํ GitBook์ด ๋ง์์ ๋ค์ง ์์์ต๋๋ค. ๋ด ํ์์ ๋ง์ง ์์๊ณ ์คํ์ผ์ด ๋ง์์ ๋ค์ง ์์๊ณ ๋งํฌ๋ฅผ ์กฐ์ ํ๊ณ ์์ ํ๋ ๋ฐ ๋๋ฌด ๋ง์ ์๊ฐ์ ์๋นํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์. ๋ง์ถคํ ๋ฌธ์๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๊ณ ๋๋๊ฒ๋ ๋ชจ๋ ๊ฒ์ด ์์กฐ๋กญ๊ฒ ์งํ๋์์ต๋๋ค! ์ ์ผํ ์ฝ์ํธ๋ ๋ชจ๋ ๋ฌธ์ํ ์๋ฃจ์ ์์ ์ฌ์ฉํ ์ ์๋ ์ฐ์ํ ๊ฒ์ ๊ธฐ๋ฅ์ด ๋ถ์กฑํ๊ฑฐ๋ open-source Docusaurus .
์ค์น
Algolia ๊ฒ์ ๊ตฌ์ฑ ์์ ๋ฐ Algolia ํด๋ผ์ด์ธํธ ์ค์น
npm install react-instantsearch-dom algoliasearch --save
gray-matter Markdown ํ์ ๋ฐ globby ํ์ผ ์ฐพ๊ธฐ
npm install gray-matter globby --saveDev
๊ตฌ์ฑ
import algoliasearch from 'algoliasearch/lite';
import {connectStateResults, Hits, InstantSearch, SearchBox} from 'react-instantsearch-dom';
const searchClient = algoliasearch(
'APP_ID',
'SEARCH_API_KEY'
);
๊ฒฐ๊ณผ ๊ตฌ์ฑ ์์
๊ธฐ๋ณธ์ ์ผ๋ก Algolia
<Hits/>
๊ตฌ์ฑ ์์๋ ๊ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋น์ด ์์ ๋ ๋ชจ๋ ๊ฒฐ๊ณผ๋ฅผ ํ์ํฉ๋๋ค. ๊ฒฐ๊ณผ๊ฐ ์๊ณ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๊ฒฝ์ฐ์๋ง ๊ฒฐ๊ณผ๊ฐ ํ์๋๋๋ก ์ฝ๋๋ฅผ ์กฐ์ ํ์ต๋๋ค.const Results = connectStateResults(({searchState, searchResults, searching}) => {
const hasQuery = searchState && searchState.query;
const hasResults = (searchResults?.hits ?? []).length > 0;
const isSearching = searching;
if (hasQuery && hasResults) {
return <Hits hitComponent={Hit}/>;
}
if (hasQuery && !hasResults && !isSearching) {
return <div>No results ๐</div>
}
return null;
}
);
์ ์ค ๊ตฌ์ฑ ์์
์ ์ค ๊ตฌ์ฑ ์์๋ ํ๋์ ๊ฒ์ ๊ฒฐ๊ณผ์ผ ๋ฟ์ ๋๋ค. ๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ๊ฐ ๋ ์ฝ๋๋ก ์คํ์ผ์ ์ ๊ฑฐํ์ต๋๋ค. ๐
import React from "react";
function Hit(props: any) {
const content = props?.hit?.content ?? "";
const words = content.split(" ").length;
return (<a href={props.hit.slug}>
<div>
<h3>{props?.hit?.frontmatter?.title ?? "no title"}</h3>
</div>
<p>{props?.hit?.frontmatter?.excerpt ?? ""}</p>
</a>)
}
export default Hit;
๊ฒ์ ๊ตฌ์ฑ์์
์ด์ ๋จ๊ณ์์ ๊ตฌ์ฑํ
InstantSearch
๊ตฌ์ฑ ์์ ๋ฐ ๊ฒ์ ํด๋ผ์ด์ธํธ๋ฅผ ์ฌ์ฉํ๊ณ ์ธ๋ฑ์ค ์ด๋ฆ์ ์
๋ ฅํ๊ณ AlgoliaSearchBox
๊ตฌ์ฑ ์์์ ์ด์ ์ ๋ง๋ ๋น ์ํ ๋์์ด ๋ณ๊ฒฝ๋ ์ฌ์ฉ์ ์ง์ Results
๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํฉ๋๋ค.<InstantSearch indexName="simplelocalize-docs" searchClient={searchClient}>
<SearchBox/>
<Results/>
</InstantSearch>
ํ์ ์ด์์ผ๋ก ๋ณต์กํ๊ฒ ๋ง๋ค๊ณ ์ถ์ง ์์์ต๋๋ค. ๊ฒ์ ๋ฒํผ๊ณผ ์ฌ์ค์ ๋ฒํผ์ ์จ๊ธฐ๊ธฐ ์ํด ์คํ์ผ์ํธ์ ์ผ๋ถ CSS ์์ฑ์ ๋ฎ์ด์๋๋ค. ๋ํ SCSS
@extend
์์ฑ์ ์ฌ์ฉํ์ฌ ๊ฒ์์ฐฝ์ ๋ถํธ์คํธ๋ฉ ์คํ์ผ์ ์ถ๊ฐํ์ต๋๋ค. ๊ฐ๋จํ๊ณ ์์
์ ์ํํฉ๋๋ค..ais-SearchBox-input {
@extend .form-control;
}
.ais-SearchBox-submit {
display: none;
}
.ais-SearchBox-reset {
display: none;
}
์ธ๋ฑ์ฑํ ๋งํฌ๋ค์ด ํ์ผ ๊ฐ์ ธ์ค๊ธฐ
์ด์ ์ธ๋ฑ์ฑ๋๊ณ ๊ฒ์ ๊ฒฐ๊ณผ์ ํ์๋์ด์ผ ํ๋ ๋ชจ๋ ํ์ผ์ ๊ฐ์ ธ์ฌ ๋์ ๋๋ค. CI/CD ์๋ฒ์์ ์ฑ๊ณต์ ์ผ๋ก ๋น๋ํ ๋๋ง๋ค ์คํํ ์
index-docs.js
ํ์ผ์ ๋ง๋ค์์ต๋๋ค. ๋ชจ๋ ๋ฌธ์ ํ์ด์ง๋ /docs/
๋๋ ํ ๋ฆฌ์ ์์ต๋๋ค. ๊ทธ๋์ Algolia ๊ฒ์ ์์ธ์ ์ฑ์ฐ๊ธฐ ์ํด ๊ฐ์ฒด ๋ฐฐ์ด๋ก ๋ณํํ๊ธฐ ์ํ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํด์ผ ํ์ต๋๋ค.import {globby} from 'globby';
import fs from "fs";
import matter from "gray-matter";
import algoliasearch from 'algoliasearch';
const pages = await globby([
'docs/',
]);
const objects = pages.map(page => {
const fileContents = fs.readFileSync(page, 'utf8')
const {data, content} = matter(fileContents)
const path = page.replace('.md', '');
let slug = path === 'docs/index' ? 'docs' : path;
slug = "/" + slug + "/"
return {
slug,
content,
frontmatter: {
...data
}
}
})
//algolia update index code
/docs/
, globby
๋๋ ํ ๋ฆฌ์์ ๋ชจ๋ ๋งํฌ๋ค์ด ํ์ผ์ ๊ฐ์ ธ์ต๋๋ค.fs
, gray-matter
๋ก ํ์ผ ๊ตฌ๋ฌธ ๋ถ์, ํ์์ ๊ฒฝ์ฐ ์ฌ๋ฌ๊ทธ๋ ํ์ผ ๊ฒฝ๋ก์ผ ๋ฟ์ด๋ฉฐ ํ์ผ ๊ฒฝ๋ก๋
/docs/{category}/{title}.md
, ์: /docs/integrations/next-translate.md
์ด๋ฏ๋ก ์ฌ๋ฌ๊ทธ๋ ๋ค์๊ณผ ๊ฐ์ด ํ์๋ฉ๋๋ค. /docs/integrations/next-translate/
๊ฒฐ๊ตญ ๋๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ฒด ๋ฐฐ์ด์ ์ป์ต๋๋ค.
[
{
"slug": "/docs/integrations/next-translate/",
"content": "My article content for indexing",
"frontmatter": {
"category": "Integrations",
"date": "2022-01-20",
"some-other-properties": "properties"
}
}
]
Algolia ์ง์ ์ ๋ฐ์ดํธ
Algolia๋ ์ ๋ง ๊ฐ๋จํ๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์ด JavaScript ํด๋ผ์ด์ธํธ๋ฅผ ์ ๊ณตํ๋ฏ๋ก ์ฌ๊ธฐ์ ๋ง๋ฒ์ด ์์ต๋๋ค. ์ธ๋ฑ์ค๋ฅผ ํ๋ํ๊ณ ์ด์ ๋จ๊ณ์์ ์์ฑํ ๊ฐ์ฒด๋ฅผ ์ ์ฅํฉ๋๋ค.
const client = algoliasearch(
'APP_ID',
'ADMIN_API_KEY'
);
const index = client.initIndex("simplelocalize-docs")
index.saveObjects(objects, {
autoGenerateObjectIDIfNotExist: true
});
package.json์ ์ธ๋ฑ์ค ์ ๋ฐ์ดํธ ํฌํจ
{
"name": "simplelocalize-docs",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export -o build/ && i18n:download && index:docs",
"index:docs": "npx ts-node --skip-project index-docs.mjs",
"i18n:upload": "simplelocalize upload",
"i18n:download": "simplelocalize download"
}
}
npm run index:docs
๋ฅผ ์คํํ์ฌ ์คํฌ๋ฆฝํธ๋ฅผ ์๋์ผ๋ก ์คํํ ์ ์์ต๋๋ค.๊ทธ๊ฒ ๋ค์ผ? ๊ทธ๊ฒ ๋ค์ผ! SimpleLocalizedocumentation page์์ ์คํ ์ค์ธ ๊ฒ์์ ์ฐธ์กฐํ์ญ์์ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐ Algolia ๊ฒ์์ NextJS ๋ฐ ๋งํฌ๋ค์ด ํ์ผ๊ณผ ์ด๋ป๊ฒ ํตํฉํ์ต๋๊น?), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/jpomykala/how-i-integrated-algolia-search-with-nextjs-and-markdown-files-4mapํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค