Comments & Search for Any SSG
Add real-time comments and full-text search to Hugo, Astro, Hexo, or any static site generator with RaisFast's universal API.
Overview
RaisFast provides a universal REST API for comments and full-text search that works with any static site generator. This guide shows you how to integrate both features into any SSG — Hugo, Astro, Hexo, Eleventy, Jekyll, Next.js static export, or anything else.
Your SSG → generates static HTML
RaisFast → comments API + search API (via fetch / JavaScript)Prerequisites
- RaisFast installed (Installation Guide →)
- Any static site generator
Step 1: Start RaisFast
raisfastThe API is available at http://localhost:9898/api/v1.
Step 2: Add Comments (Universal)
Create a raisfast-comments.js file and include it on every page where you want comments:
<!-- Add to your article/post template -->
<div id="raisfast-comments" data-page-id="{{ .RelPermalink }}"></div>
<script src="/js/raisfast-comments.js"></script>// static/js/raisfast-comments.js
(function () {
var API_BASE = "http://localhost:9898/api/v1";
var container = document.getElementById("raisfast-comments");
if (!container) return;
var pageId = container.dataset.pageId;
function renderComments(items) {
return items
.map(function (c) {
return (
'<div class="rf-comment" style={{marginBottom: "16px"}}>' +
"<strong>" +
c.author_name +
"</strong>" +
"<span style={{marginLeft: "8px", color: "#999", fontSize: "0.85em"}}>" +
new Date(c.created_at).toLocaleDateString() +
"</span>" +
"<p>" +
c.content +
"</p>" +
"</div>"
);
})
.join("");
}
function renderForm() {
return (
'<form id="rf-comment-form" style={{"marginTop": "16px"}}>' +
'<input name="author_name" placeholder="Your name" required style={{display: "block", marginBottom: "8px", padding: "8px", width: "100%", maxWidth: "400px"}}>' +
'<textarea name="content" placeholder="Write a comment..." required style={{display: "block", marginBottom: "8px", padding: "8px", width: "100%", maxWidth: "600px", minHeight: "80px"}}></textarea>' +
'<button type="submit">Post Comment</button>' +
"</form>"
);
}
fetch(API_BASE + "/comments?post_id=" + encodeURIComponent(pageId))
.then(function (r) {
return r.json();
})
.then(function (data) {
var items = (data.data && data.data.items) || [];
container.innerHTML =
'<h3>Comments (' + items.length + ")</h3>" +
renderComments(items) +
renderForm();
document
.getElementById("rf-comment-form")
.addEventListener("submit", function (e) {
e.preventDefault();
var form = this;
fetch(API_BASE + "/comments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
post_id: pageId,
author_name: form.author_name.value,
content: form.content.value,
}),
}).then(function () {
location.reload();
});
});
});
})();Framework-specific page ID
The data-page-id attribute should uniquely identify each page:
| SSG | Template Variable |
|---|---|
| Hugo | {{ .RelPermalink }} |
| Astro | {Astro.url.pathname} |
| Hexo | <%= page.path %> |
| Eleventy | {{ page.url }} |
| Jekyll | {{ page.url }} |
| Next.js (static) | window.location.pathname |
Step 3: Add Search (Universal)
Create a raisfast-search.js file and add a search input to your site:
<!-- Add to your layout header or navigation -->
<div id="raisfast-search">
<input type="text" id="rf-search-input" placeholder="Search...">
<div id="rf-search-results"></div>
</div>
<script src="/js/raisfast-search.js"></script>// static/js/raisfast-search.js
(function () {
var API_BASE = "http://localhost:9898/api/v1";
var input = document.getElementById("rf-search-input");
var results = document.getElementById("rf-search-results");
if (!input || !results) return;
var debounceTimer;
input.addEventListener("input", function () {
var query = this.value.trim();
clearTimeout(debounceTimer);
if (query.length < 2) {
results.innerHTML = "";
return;
}
debounceTimer = setTimeout(function () {
fetch(
API_BASE + "/search?q=" + encodeURIComponent(query)
)
.then(function (r) {
return r.json();
})
.then(function (data) {
var items = (data.data && data.data.items) || [];
if (items.length === 0) {
results.innerHTML = '<p style={{"padding": "8px", "color": "#999"}}>No results found.</p>';
return;
}
results.innerHTML = items
.map(function (p) {
return (
'<a href="' +
p.url +
'" style={{display: "block", padding: "8px", textDecoration: "none", color: "inherit"}}>' +
"<strong>" +
p.title +
"</strong>" +
"<p style={{margin: "4px 0 0", fontSize: "0.85em", color: "#666"}}>" +
p.excerpt +
"</p>" +
"</a>"
);
})
.join("");
});
}, 300);
});
document.addEventListener("click", function (e) {
if (
!input.contains(e.target) &&
!results.contains(e.target)
) {
results.innerHTML = "";
input.value = "";
}
});
})();Step 4: Index Your Content
Before search works, you need to push your content into RaisFast's search index. Create a build script:
// scripts/index-content.js
// Run after: hugo build / astro build / hexo generate
// Usage: node scripts/index-content.js
var fs = require("fs");
var path = require("path");
var API_BASE = "http://localhost:9898/api/v1";
function walkDir(dir, ext) {
var results = [];
var list = fs.readdirSync(dir);
list.forEach(function (file) {
var filePath = path.join(dir, file);
var stat = fs.statSync(filePath);
if (stat && stat.isDirectory()) {
results = results.concat(walkDir(filePath, ext));
} else if (file.endsWith(ext)) {
results.push(filePath);
}
});
return results;
}
function extractTitle(html) {
var match = html.match(/<h1[^>]*>(.*?)<\/h1>/);
return match ? match[1] : "";
}
function stripHtml(html) {
return html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
}
var outputDir = process.argv[2] || "./public";
var htmlFiles = walkDir(outputDir, ".html");
htmlFiles.forEach(function (file) {
var html = fs.readFileSync(file, "utf-8");
var title = extractTitle(html) || path.basename(file, ".html");
var content = stripHtml(html).substring(0, 5000);
var url = "/" + path.relative(outputDir, file);
fetch(API_BASE + "/search/index", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: title,
content: content,
url: url,
}),
});
});
console.log("Indexed " + htmlFiles.length + " pages.");Add to your build pipeline:
# Build your static site first
hugo build # or: astro build / hexo generate
# Then index content into RaisFast
node scripts/index-content.js ./publicStep 5: Deploy
Production Architecture
Internet
├── Nginx / Caddy (reverse proxy)
│ ├── / → static files (Hugo/Astro/Hexo output)
│ └── /api/ → RaisFast (127.0.0.1:9898)
│
└── RaisFast (API + Admin)
├── Comments API
├── Search API
└── Admin panel at /adminNginx Config
server {
listen 80;
server_name yourdomain.com;
# Static files from your SSG
location / {
root /var/www/site/public;
try_files $uri $uri/ /index.html;
}
# RaisFast API
location /api/ {
proxy_pass http://127.0.0.1:9898;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# RaisFast Admin (optional)
location /admin {
proxy_pass http://127.0.0.1:9898;
}
}Caddy Config
yourdomain.com {
root * /var/www/site/public
try_files {path} /index.html
file_server
reverse_proxy /api/* localhost:9898
reverse_proxy /admin localhost:9898
}Customization
Styling the Comments Widget
Add your own CSS to match your site's design:
#raisfast-comments {
max-width: 720px;
margin: 2rem auto;
}
.rf-comment {
padding: 12px 0;
border-bottom: 1px solid #eee;
}
#rf-comment-form input,
#rf-comment-form textarea {
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
}
#rf-comment-form button {
background: #0070f3;
color: white;
border: none;
padding: 8px 24px;
border-radius: 4px;
cursor: pointer;
}Styling the Search Widget
#raisfast-search {
position: relative;
}
#rf-search-input {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 8px;
width: 240px;
font-size: 14px;
}
#rf-search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
max-height: 400px;
overflow-y: auto;
z-index: 100;
}
#rf-search-results a:hover {
background: #f5f5f5;
}Going Further
- Hugo + RaisFast → — Hugo-specific integration guide
- Astro + RaisFast → — Astro-specific integration guide
- Hexo + RaisFast → — Hexo-specific integration guide
- API Reference → — Full API documentation for comments and search endpoints
