400 lines
11 KiB
Markdown
400 lines
11 KiB
Markdown
# Hugo WordPress Integration Guide
|
|
|
|
This guide provides step-by-step instructions for setting up a Hugo static site that fetches and displays blog articles from a WordPress API.
|
|
|
|
## Project Overview
|
|
|
|
Your Hugo project will act as a static site generator that pulls content from a WordPress REST API and generates a fast, SEO-friendly blog. This approach combines the performance benefits of static sites with the content management capabilities of WordPress.
|
|
|
|
## Prerequisites
|
|
|
|
- Hugo installed (v0.110.0 or later)
|
|
- WordPress site with REST API enabled
|
|
- Basic knowledge of Go templates and Hugo
|
|
- Node.js (for build scripts)
|
|
|
|
## Step 1: Configure Hugo for WordPress API Integration
|
|
|
|
### Update hugo.toml Configuration
|
|
|
|
Add the following configuration to your [`hugo.toml`](hugo.toml:1) file:
|
|
|
|
```toml
|
|
# Base configuration
|
|
baseURL = "https://yourblog.com"
|
|
languageCode = "en-us"
|
|
title = "Your Blog Title"
|
|
theme = "your-theme"
|
|
|
|
# WordPress API Configuration
|
|
[params.wordpress]
|
|
apiUrl = "https://your-wordpress-site.com/wp-json/wp/v2"
|
|
postsPerPage = 10
|
|
featuredImageSize = "large"
|
|
authorEndpoint = "users"
|
|
categoryEndpoint = "categories"
|
|
tagEndpoint = "tags"
|
|
|
|
# Build configuration
|
|
[build]
|
|
writeStats = true
|
|
[[build.cachebusters]]
|
|
source = "assets/.*\\.(js|ts|jsx|ts)$"
|
|
target = "js"
|
|
[[build.cachebusters]]
|
|
source = "assets/.*\\.(css|sass|scss)$"
|
|
target = "css"
|
|
```
|
|
|
|
## Step 2: Create Data Fetching Scripts
|
|
|
|
### Create API Fetching Script
|
|
|
|
Create a new file [`scripts/fetch-wordpress.js`](scripts/fetch-wordpress.js:1):
|
|
|
|
```javascript
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const fetch = require('node-fetch');
|
|
|
|
const WORDPRESS_API = 'https://www.mistergeek.net//wp-json/wp/v2';
|
|
const OUTPUT_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
|
|
|
async function fetchPosts(page = 1, perPage = 100) {
|
|
const response = await fetch(`${WORDPRESS_API}/posts?page=${page}&per_page=${perPage}&_embed`);
|
|
const posts = await response.json();
|
|
|
|
if (response.headers.get('x-wp-totalpages') > page) {
|
|
const nextPosts = await fetchPosts(page + 1, perPage);
|
|
return [...posts, ...nextPosts];
|
|
}
|
|
|
|
return posts;
|
|
}
|
|
|
|
async function fetchCategories() {
|
|
const response = await fetch(`${WORDPRESS_API}/categories?per_page=100`);
|
|
return response.json();
|
|
}
|
|
|
|
async function fetchTags() {
|
|
const response = await fetch(`${WORDPRESS_API}/tags?per_page=100`);
|
|
return response.json();
|
|
}
|
|
|
|
async function fetchAuthors() {
|
|
const response = await fetch(`${WORDPRESS_API}/users?per_page=100`);
|
|
return response.json();
|
|
}
|
|
|
|
async function generateData() {
|
|
// Ensure output directory exists
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
|
|
console.log('Fetching WordPress data...');
|
|
|
|
const [posts, categories, tags, authors] = await Promise.all([
|
|
fetchPosts(),
|
|
fetchCategories(),
|
|
fetchTags(),
|
|
fetchAuthors()
|
|
]);
|
|
|
|
// Save data as JSON files
|
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'posts.json'), JSON.stringify(posts, null, 2));
|
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'categories.json'), JSON.stringify(categories, null, 2));
|
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'tags.json'), JSON.stringify(tags, null, 2));
|
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'authors.json'), JSON.stringify(authors, null, 2));
|
|
|
|
console.log(`✅ Fetched ${posts.length} posts, ${categories.length} categories, ${tags.length} tags, ${authors.length} authors`);
|
|
}
|
|
|
|
generateData().catch(console.error);
|
|
```
|
|
|
|
### Create Package.json for Build Scripts
|
|
|
|
Create [`package.json`](package.json:1):
|
|
|
|
```json
|
|
{
|
|
"name": "hugo-wordpress-blog",
|
|
"version": "1.0.0",
|
|
"description": "Hugo static site with WordPress content",
|
|
"scripts": {
|
|
"fetch-data": "node scripts/fetch-wordpress.js",
|
|
"build": "npm run fetch-data && hugo --minify",
|
|
"dev": "npm run fetch-data && hugo server -D"
|
|
},
|
|
"dependencies": {
|
|
"node-fetch": "^3.3.2"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 3: Create Hugo Templates for WordPress Content
|
|
|
|
### Create Content Layout
|
|
|
|
Create [`layouts/_default/single.html`](layouts/_default/single.html:1):
|
|
|
|
```html
|
|
{{ define "main" }}
|
|
<article class="post">
|
|
<header class="post-header">
|
|
<h1 class="post-title">{{ .Title }}</h1>
|
|
<div class="post-meta">
|
|
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
|
|
{{ if .Params.author }}
|
|
<span class="post-author">{{ .Params.author.name }}</span>
|
|
{{ end }}
|
|
{{ if .Params.categories }}
|
|
<div class="post-categories">
|
|
{{ range .Params.categories }}
|
|
<a href="/categories/{{ .slug }}" class="category">{{ .name }}</a>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
{{ if .Params.featured_image }}
|
|
<img src="{{ .Params.featured_image }}" alt="{{ .Title }}" class="featured-image">
|
|
{{ end }}
|
|
</header>
|
|
|
|
<div class="post-content">
|
|
{{ .Content }}
|
|
</div>
|
|
|
|
{{ if .Params.tags }}
|
|
<div class="post-tags">
|
|
{{ range .Params.tags }}
|
|
<a href="/tags/{{ .slug }}" class="tag">#{{ .name }}</a>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
</article>
|
|
{{ end }}
|
|
```
|
|
|
|
### Create List Layout
|
|
|
|
Create [`layouts/_default/list.html`](layouts/_default/list.html:1):
|
|
|
|
```html
|
|
{{ define "main" }}
|
|
<div class="posts-list">
|
|
<h1>{{ .Title }}</h1>
|
|
|
|
{{ range .Pages }}
|
|
<article class="post-preview">
|
|
<h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
|
|
<div class="post-meta">
|
|
<time>{{ .Date.Format "Jan 2, 2006" }}</time>
|
|
{{ if .Params.author }}
|
|
<span>{{ .Params.author.name }}</span>
|
|
{{ end }}
|
|
</div>
|
|
{{ if .Params.excerpt }}
|
|
<p class="post-excerpt">{{ .Params.excerpt }}</p>
|
|
{{ end }}
|
|
<a href="{{ .RelPermalink }}" class="read-more">Read more →</a>
|
|
</article>
|
|
{{ end }}
|
|
</div>
|
|
{{ end }}
|
|
```
|
|
|
|
## Step 4: Create Hugo Content from WordPress Data
|
|
|
|
### Create Content Generator Script
|
|
|
|
Create [`scripts/generate-content.js`](scripts/generate-content.js:1):
|
|
|
|
```javascript
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const DATA_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
|
const CONTENT_DIR = path.join(__dirname, '..', 'content', 'posts');
|
|
|
|
function generateContent() {
|
|
const posts = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'posts.json'), 'utf8'));
|
|
|
|
// Ensure content directory exists
|
|
if (!fs.existsSync(CONTENT_DIR)) {
|
|
fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
|
}
|
|
|
|
posts.forEach(post => {
|
|
const slug = post.slug;
|
|
const date = new Date(post.date);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
const contentDir = path.join(CONTENT_DIR, `${year}-${month}-${slug}`);
|
|
|
|
if (!fs.existsSync(contentDir)) {
|
|
fs.mkdirSync(contentDir, { recursive: true });
|
|
}
|
|
|
|
const frontmatter = {
|
|
title: post.title.rendered,
|
|
date: post.date,
|
|
draft: post.status !== 'publish',
|
|
slug: slug,
|
|
wordpress_id: post.id,
|
|
excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, ''),
|
|
featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url || '',
|
|
author: {
|
|
id: post.author,
|
|
name: post._embedded?.author?.[0]?.name || 'Unknown'
|
|
},
|
|
categories: post._embedded?.['wp:term']?.[0]?.map(cat => ({
|
|
id: cat.id,
|
|
name: cat.name,
|
|
slug: cat.slug
|
|
})) || [],
|
|
tags: post._embedded?.['wp:term']?.[1]?.map(tag => ({
|
|
id: tag.id,
|
|
name: tag.name,
|
|
slug: tag.slug
|
|
})) || []
|
|
};
|
|
|
|
const content = `---
|
|
${Object.entries(frontmatter)
|
|
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
.join('\n')}
|
|
---
|
|
|
|
${post.content.rendered}`;
|
|
|
|
fs.writeFileSync(path.join(contentDir, 'index.md'), content);
|
|
});
|
|
|
|
console.log(`✅ Generated ${posts.length} content files`);
|
|
}
|
|
|
|
generateContent();
|
|
```
|
|
|
|
## Step 5: Update Build Process
|
|
|
|
### Create Build Script
|
|
|
|
Update your [`package.json`](package.json:1) build scripts:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"fetch-data": "node scripts/fetch-wordpress.js",
|
|
"generate-content": "node scripts/generate-content.js",
|
|
"prebuild": "npm run fetch-data && npm run generate-content",
|
|
"build": "hugo --minify",
|
|
"dev": "npm run fetch-data && npm run generate-content && hugo server -D",
|
|
"clean": "rm -rf data/wordpress content/posts public"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 6: Configure Netlify for Automated Builds
|
|
|
|
Create [`netlify.toml`](netlify.toml:1):
|
|
|
|
```toml
|
|
[build]
|
|
command = "npm run build"
|
|
publish = "public"
|
|
|
|
[build.environment]
|
|
HUGO_VERSION = "0.110.0"
|
|
NODE_VERSION = "18"
|
|
|
|
[[plugins]]
|
|
package = "@netlify/plugin-sitemap"
|
|
|
|
[[plugins]]
|
|
package = "netlify-plugin-hugo-cache-resources"
|
|
```
|
|
|
|
## Step 7: Advanced Features
|
|
|
|
### Add Search Functionality
|
|
|
|
Create [`layouts/partials/search.html`](layouts/partials/search.html:1):
|
|
|
|
```html
|
|
<div class="search-container">
|
|
<input type="search" id="search" placeholder="Search articles..." aria-label="Search">
|
|
<div id="search-results" class="search-results"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Add client-side search using Fuse.js
|
|
</script>
|
|
```
|
|
|
|
### Add RSS Feed
|
|
|
|
Create [`layouts/index.rss.xml`](layouts/index.rss.xml:1):
|
|
|
|
```xml
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>{{ .Site.Title }}</title>
|
|
<link>{{ .Site.BaseURL }}</link>
|
|
<description>{{ .Site.Params.description }}</description>
|
|
<language>{{ .Site.LanguageCode }}</language>
|
|
{{ range first 15 .Pages }}
|
|
<item>
|
|
<title>{{ .Title }}</title>
|
|
<link>{{ .Permalink }}</link>
|
|
<description>{{ .Params.excerpt }}</description>
|
|
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</pubDate>
|
|
<guid>{{ .Permalink }}</guid>
|
|
</item>
|
|
{{ end }}
|
|
</channel>
|
|
</rss>
|
|
```
|
|
|
|
## Deployment Options
|
|
|
|
### Netlify
|
|
1. Connect your GitHub repository
|
|
2. Set build command: `npm run build`
|
|
3. Set publish directory: `public`
|
|
|
|
### Vercel
|
|
1. Install Vercel CLI: `npm i -g vercel`
|
|
2. Run: `vercel --prod`
|
|
|
|
### GitHub Pages
|
|
1. Create `.github/workflows/deploy.yml`
|
|
2. Use GitHub Actions for automated deployment
|
|
|
|
## Performance Optimization
|
|
|
|
1. **Image Optimization**: Use Hugo's image processing
|
|
2. **CDN**: Configure Cloudflare or similar
|
|
3. **Caching**: Implement proper cache headers
|
|
4. **Minification**: Enable HTML/CSS/JS minification
|
|
5. **Preloading**: Add resource hints for critical assets
|
|
|
|
## Next Steps
|
|
|
|
1. Run `npm install` to install dependencies
|
|
2. Run `npm run dev` to start development server
|
|
3. Customize your theme and styling
|
|
4. Set up automated deployment
|
|
5. Configure SEO meta tags
|
|
6. Add analytics (Google Analytics, Plausible, etc.)
|
|
|
|
## Troubleshooting
|
|
|
|
- **API Rate Limits**: Implement caching and rate limiting
|
|
- **Image Loading**: Ensure WordPress images are accessible
|
|
- **CORS Issues**: Configure WordPress CORS headers
|
|
- **Build Time**: Consider incremental builds for large sites |