first commit

This commit is contained in:
kbe
2025-08-18 15:31:01 +02:00
commit 316e12b98a
10 changed files with 788 additions and 0 deletions

160
.gitignore vendored Normal file
View File

@@ -0,0 +1,160 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,hugo
# Edit at https://www.toptal.com/developers/gitignore?templates=node,hugo
### Hugo ###
# Generated files by hugo
/public/
/resources/_gen/
/assets/jsconfig.json
hugo_stats.json
# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux
# Temporary lock file while building
/.hugo_build.lock
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node,hugo

5
archetypes/default.md Normal file
View File

@@ -0,0 +1,5 @@
+++
date = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+++

View File

@@ -0,0 +1,400 @@
# 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

17
docs/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "hugo-wordpress-blog",
"version": "1.0.0",
"description": "Hugo static site with WordPress content",
{
"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"
}
"dependencies": {
"node-fetch": "^3.3.2"
}
}

23
hugo.toml Normal file
View File

@@ -0,0 +1,23 @@
baseURL = 'https://www.mistergeek.net/'
languageCode = 'fr-fr'
title = 'Mistergeek'
# theme = "your-theme"
# WordPress API Configuration
[params.wordpress]
apiUrl = "https://www.mistergeek.net/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"

View File

@@ -0,0 +1,20 @@
<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 }}

View File

@@ -0,0 +1,35 @@
{{ 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 }}

View File

@@ -0,0 +1,59 @@
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);

View File

@@ -0,0 +1,65 @@
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();

4
yarn.lock Normal file
View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1