Go Back

TECH+++

Article

How to Implement AI Features in a NextJS Blog

AI is changing how users interact with content online. If you have a blog built with NextJS, integrating AI-powered features like search, chatbots, and content recommendations can boost user engagement and accessibility. This guide walks through implementing these features using open-source and cost-effective solutions.

In this guide you will learn how to set up a very basic blog and implement an AI feature like a chatbot, let's start.

Why Use NextJS?

Next.js is a powerful React framework that offers server-side rendering (SSR), static site generation (SSG), and API routes out of the box. These features make it an excellent choice for an AI-powered blog by improving performance, SEO, and user experience. Additionally, Next.js seamlessly integrates with various AI tools, APIs, and serverless functions, making it easy to build dynamic and intelligent web applications.

Why Use the App Router Instead of the Page Router?

Next.js introduced the App Router as a modern way to build applications, replacing the Page Router. Here’s why you should use the App Router for your AI-powered blog:

  • Server Components: Reduce client-side JavaScript and improve performance.

  • Streaming and Suspense: Load parts of your page progressively for a smoother user experience.

  • Simplified Data Fetching: Use React’s use hook for fetching data in server components.

  • Better API and Middleware Support: Easier handling of backend logic and API routes.

  • File-based Routing with Layouts: Organize UI components more efficiently.

By leveraging the App Router, you get a more scalable, maintainable, and performant blog setup.

1. Setting Up a NextJS Blog with Markdown

To follow this guide, first, set up a simple blog where each post is stored as a Markdown file.

  • Steps to Create a Markdown-Based Blog
1npx create-next-app@latest my-blog --experimental-app 2cd my-blog 3npm install gray-matter react-markdown
  • Organize Blog Content Inside the content/articles folder, create Markdown files (.md) for each blog post.
1mkdir -p content/articles 2echo "---\ntitle: 'My First Post'\ndate: '2025-04-01'\ntags: ['nextjs', 'blog']\n---\n\nThis is my first blog post." > content/articles/post-1.md
  • Load Markdown Content Create a directory named lib inside that create a article.ts file that is going to handle the Markdown files and convert them into structured data
1import fs from "fs"; 2import path from "path"; 3import matter from "gray-matter"; 4 5const ARTICLES_DIR = path.join(process.cwd(), "content/articles"); 6 7export function getAllArticles() { 8 return fs.readdirSync(ARTICLES_DIR).map((filename) => ({ 9 slug: filename.replace(".md", ""), 10 ...getArticle(filename.replace(".md", "")), 11 })); 12} 13 14export function getArticle(slug: string) { 15 const filePath = path.join(ARTICLES_DIR, `${slug}.md`); 16 17 if (!fs.existsSync(filePath)) return null; 18 19 const fileContents = fs.readFileSync(filePath, "utf8"); 20 const { data, content } = matter(fileContents); 21 22 return { 23 title: data.title || slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()), 24 date: data.date || "Unknown Date", 25 content, 26 description: data.description || "", 27 image: data.image || "", 28 tag: data.tag || "Uncategorized", 29 id: data.id || "", 30 author: data.author || "", 31 duration: data.duration || "", 32 category: data.category || "", 33 }; 34}
  • Create a Dynamic Blog Post Page In app/articles/[slug]/page.tsx
1import { getAllArticles } from "../../../lib/articles"; 2import { notFound } from "next/navigation"; 3import ReactMarkdown from "react-markdown"; 4 5interface Article { 6 slug: string; 7 title: string; 8 date: string; 9 content: string; 10} 11 12interface BlogPostProps { 13 params: { 14 slug: string; 15 }; 16} 17 18export default function BlogPost({ params }: BlogPostProps) { 19 const articles: Article[] = getAllArticles(); 20 const article = articles.find((a) => a.slug === params.slug); 21 22 if (!article) return notFound(); 23 24 const { title, date, content } = article; 25 26 return ( 27 <article role="article" aria-labelledby="article-title"> 28 <header> 29 <h2 id="article-title"> 30 {title} 31 </h2> 32 <time dateTime={date}> 33 {date} 34 </time> 35 </header> 36 <ReactMarkdown> 37 {content} 38 </ReactMarkdown> 39 </article> 40 ); 41}
  • List Blog Posts on the Homepage Update app/page.tsx
1import Link from "next/link"; 2 3interface Article { 4 slug: string; 5 title: string; 6} 7 8interface HomeProps { 9 articles: Article[]; 10} 11 12export default function Home({ articles }: HomeProps) { 13 return ( 14 <main role="main" aria-labelledby="blog-heading"> 15 <h1 id="blog-heading">My Blog</h1> 16 <ul aria-label="List of blog articles"> 17 {articles.map(({ slug, title }) => ( 18 <li key={slug}> 19 <Link href={`/articles/${slug}`} aria-label={`Read article: ${title}`}> 20 {title} 21 </Link> 22 </li> 23 ))} 24 </ul> 25 </main> 26 ); 27}

2. AI-Powered Search with Fuse.js

A robust search feature ensures readers find content quickly. Fuse.js is a flexible and powerful fuzzy search library that can efficiently index and search blog articles.

Why Use Fuse.js

Here's a few reasons why I'm using Fuse.js for this example:

  • Lightweight & Fast: Fuse.js is optimized for fuzzy search and does not require a backend database or indexing service, making it ideal for static site search.

  • Fuzzy Matching: Unlike simple string matching, it finds relevant results even when users make typos or use different word orders.

  • Custom Scoring & Weighting: It allows fine-tuning search relevance by assigning weights to different fields (e.g., title, tags, content).

  • Ease of Use: Simple API that integrates seamlessly with Next.js and Markdown-based blogs.

Steps to Implement Fuse.js Search

  • Install Fuse.js: Add the library to your project.
1npm install fuse.js
  • Load and Index Articles with Fuse.js:
1import Fuse from 'fuse.js'; 2import { getAllArticles } from '@/lib/articles'; 3 4const articles = getAllArticles(); 5const fuse = new Fuse(articles, { keys: ['title', 'content', 'tag'] }); 6 7export function searchArticles(query: string) { 8 return fuse.search(query).map(result => result.item); 9} 10 11// Initialize Fuse instance outside the function for performance 12let fuseInstance: Fuse<Article> | null = null; 13 14function getFuseInstance() { 15 if (!fuseInstance) { 16 const articles = getAllArticles(); 17 const options: IFuseOptions<Article> = { 18 keys: [ 19 { name: 'title', weight: 0.4 }, 20 { name: 'content', weight: 0.3 }, 21 { name: 'tag', weight: 0.2 }, 22 { name: 'description', weight: 0.1 } 23 ], 24 includeScore: true, 25 threshold: 0.4, 26 minMatchCharLength: 2, 27 }; 28 fuseInstance = new Fuse(articles, options); 29 } 30 return fuseInstance; 31} 32 33export function searchArticles(query: string): Article[] { 34 if (!query || query.trim().length < 2) { 35 return []; 36 } 37 const fuse = getFuseInstance(); 38 return fuse.search(query).map(result => result.item); 39} 40

3. AI-Powered Chatbot with Together AI

For enhancing user interaction on your blog, implementing an AI-powered chatbot can offer personalized responses to users. Together AI offers free access to various AI models, which can be leveraged to provide chatbot functionality without the cost associated with APIs like OpenAI's GPT. Here's why you might choose Together AI:

  • Free Access to Powerful Models: You can access large, powerful models for free or for very affordable prices in comparison to others like OpenAI, which competes well with other premium models.

  • Cost-Effective Scaling: Ideal for projects where scalability and cost are a concern. You don’t have to worry about usage caps or pricing increases as with other platforms.

  • Flexibility in Integration: Together AI models are easy to integrate into your blog, offering rich, dynamic responses for user queries.

Steps to Integrate Together AI Chatbot

    1. Sign Up for Together AI: Visit Together AI to get access to free models like Llama-3.3-70B-Instruct-Turbo.
    1. Create a Chatbot API Route: Set up an API route in Next.js that sends user queries to Together AI's models.

Let's create a directory inside app named api and inside that we will have /chatbot/route.ts

1import fetch from 'node-fetch'; 2 3export async function POST(request: Request) { 4 const { question } = await request.json(); 5 6 const response = await fetch("https://api.together.ai/models/meta-llama/Llama-3.3-70B-Instruct-Turbo/completions", { 7 method: 'POST', 8 headers: { 9 "Authorization": `Bearer ${process.env.TOGETHER_AI_API_KEY}`, 10 "Content-Type": "application/json", 11 }, 12 body: JSON.stringify({ 13 prompt: question, 14 max_tokens: 100, 15 }), 16 }); 17 18 const data = await response.json(); 19 return new Response(JSON.stringify(data), { 20 headers: { "Content-Type": "application/json" }, 21 }); 22} 23

Now, let's create a simple chatbot component

1import { useState } from "react"; 2 3export default function Chatbot() { 4 const [message, setMessage] = useState(""); 5 const [response, setResponse] = useState<string | null>(null); 6 const [isChatOpen, setIsChatOpen] = useState(false); 7 8 // Handle form submission 9 const handleSubmit = async (e: React.FormEvent) => { 10 e.preventDefault(); 11 12 // Ensure the message is not empty 13 if (!message.trim()) return; 14 15 const userMessage = message; 16 setMessage(""); 17 18 const res = await fetch("/api/chatbot", { 19 method: "POST", 20 body: JSON.stringify({ question: userMessage }), 21 headers: { "Content-Type": "application/json" }, 22 }); 23 24 const data = await res.json(); 25 setResponse(data.answer || "Sorry, I don't have an answer."); 26 }; 27 28 // Toggle chat window visibility 29 const toggleChatWindow = () => { 30 setIsChatOpen(!isChatOpen); 31 }; 32 33 return ( 34 <div> 35 {/* Chat Button */} 36 {!isChatOpen && ( 37 <button onClick={toggleChatWindow} aria-label="Open Chat"> 38 Chat 39 </button> 40 )} 41 42 {/* Chat Window */} 43 {isChatOpen && ( 44 <div> 45 <button onClick={toggleChatWindow} aria-label="Close Chat"> 46 X 47 </button> 48 <h3>Ask me anything!</h3> 49 <div> 50 {response && <p>Bot: {response}</p>} 51 </div> 52 <form onSubmit={handleSubmit}> 53 <input 54 type="text" 55 value={message} 56 onChange={(e) => setMessage(e.target.value)} 57 placeholder="Type your question..." 58 aria-label="Message input" 59 /> 60 <button type="submit"> 61 Send 62 </button> 63 </form> 64 </div> 65 )} 66 </div> 67 ); 68}

Finally you can render this component inside your layout component that way it renders in every page

Final Thoughts

Integrating AI-powered features like chatbots, search, and content recommendations into a Next.js blog can significantly enhance user engagement and provide a smarter user experience. By leveraging tools like Fuse.js for search and Together AI for chatbot functionality, you can offer cutting-edge, cost-effective features to your users.

Next.js provides the perfect framework for building dynamic, AI-powered web applications. With Together AI, you can make use of free models, making it an ideal choice for scaling without the cost concerns that come with other premium AI models. By following the steps outlined in this guide, you can take your blog to the next level with advanced AI-powered features.

I hope you find this article helpful, let's code a cool blog with a chatbot πŸ€–

Recent Articles

March 31st 2025

NextJS πŸ‘¨πŸ½β€πŸ’»
How to Implement AI Features in a NextJS Blog

How to Implement AI Features in a NextJS Blog

AI is transforming online content interaction. This guide shows how to integrate AI features in a NextJS blog step-by-step guide

AuthorCesar UsecheDuration~10 min

March 28th 2025

Docker πŸ‹
Why Docker & How to Dockerize a NextJS App

Why Docker & How to Dockerize a NextJS App

Learn why Docker is essential for modern development and how to containerize a NextJS app with a step-by-step guide.

AuthorCesar UsecheDuration~5 min

March 25th 2025

FrondEnd πŸ’»
The Importance of Web Accessibility

The Importance of Web Accessibility

Making websites accessible improves usability, SEO, and ensures inclusivity for all users, including those with disabilities.

AuthorCesar UsecheDuration~7 min