Skip to content

Instantly share code, notes, and snippets.

@squashfold
Created June 8, 2023 15:56
Show Gist options
  • Save squashfold/6c6cf82db8802d424f711c6356eae121 to your computer and use it in GitHub Desktop.
Save squashfold/6c6cf82db8802d424f711c6356eae121 to your computer and use it in GitHub Desktop.
Caching and Fetching posts in NextJS
import Head from 'next/head';
import { useCallback, useRef, useState, useEffect } from 'react'
import type Post from '../../interfaces/post'
import PostPreview from '../../components/post-preview'
export default function Home() {
const searchRef = useRef<HTMLInputElement>(null)
const defaultPosts = require('../../cache/data/posts').data;
const [query, setQuery] = useState('')
const [results, setResults] = useState<any[]>(defaultPosts)
const searchEndpoint = (query: string) => `/api/search?query=${query}`
const getResults = (query: string) => {
if (query.length) {
fetch(searchEndpoint(query))
.then(res => res.json())
.then(res => {
setResults(res.results.map((element) => element.item)) // Fuzzy search returns our results in a different format
})
.catch(error => {
console.error('Error fetching search results:', error)
setResults([])
})
} else {
setResults(defaultPosts)
}
};
const onChange = useCallback((event) => {
const query = event.target.value;
setQuery(query)
}, [query])
useEffect(() => {
getResults(query);
}, [query]);
return (
<>
<Head>
<title>{`Search`}</title>
</Head>
<div className={`container mx-auto px-5`}>
<h1 className={`text-5xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8`}>Search Articles</h1>
<div ref={searchRef} className={`mb-4`}>
<div>
<label htmlFor="searchInput" className={`mr-4`}>Filter:</label>
<input
className={`p-2 border-solid border-2 border-black`}
onChange={onChange}
placeholder='Search posts'
type='text'
value={query}
id="searchInput"
/>
</div>
</div>
<div className={`grid grid-cols-3 gap-4`}>
{results.map((post: Post, index) => (
<PostPreview
key={post.slug}
title={post.title}
coverImage={post.coverImage}
date={post.date}
author={post.author}
slug={post.slug}
excerpt={post.excerpt}
/>
))}
</div>
</div>
</>
)
}
const path = require('path')
const fs = require('fs')
const greyMatter = require('gray-matter')
function getPostData() {
const postsDirectory = path.join(process.cwd(), '_posts')
const fileNames = fs.readdirSync(postsDirectory)
const posts = fileNames.map((fileName) => {
const postId = fileName.replace(/\.md$/, '')
const fullPath = path.join(postsDirectory, fileName)
const postData = fs.readFileSync(fullPath, 'utf8')
const result = greyMatter(postData)
return {
postId,
slug: postId,
title: result.data.title,
coverImage: result.data.coverImage,
excerpt: result.data.excerpt,
date: result.data.date,
author: result.data.author
}
})
const postsData = `export const data = ${JSON.stringify(posts)}`
return postsData
}
try {
fs.readdirSync('cache')
} catch (e) {
fs.mkdirSync('cache')
}
fs.mkdir(path.join(__dirname, 'data'),
{ recursive: true }, (error) => {
if (error) {
return console.error(error)
}
console.log('Directory created successfully!')
fs.writeFile(`${path.join(__dirname, 'data')}/posts.js`, getPostData(), function (error) {
if (error) {
return console.log(error)
}
console.log(`Posts cached!`)
})
});
import type { NextApiRequest, NextApiResponse } from 'next'
import type Post from '../../interfaces/post'
import Fuse from 'fuse.js'
const posts = require('../../cache/data/posts').data
export default (req: NextApiRequest, res: NextApiResponse) => {
const fuse = new Fuse(posts, {keys: ['title', 'excerpt']})
const query = req.query.query ? req.query.query.toString().toLowerCase() : ''
const results: Post[] = query.length ? fuse.search(query) : posts
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ results }))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment