commit 316e12b98a3f22f9ddb2404d5287e036b1b7c195 Author: kbe Date: Mon Aug 18 15:31:01 2025 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2ed74a --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..25b6752 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +date = '{{ .Date }}' +draft = true +title = '{{ replace .File.ContentBaseName "-" " " | title }}' ++++ diff --git a/docs/hugo-wordpress-integration.md b/docs/hugo-wordpress-integration.md new file mode 100644 index 0000000..79d94e8 --- /dev/null +++ b/docs/hugo-wordpress-integration.md @@ -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" }} +
+
+

{{ .Title }}

+ + {{ if .Params.featured_image }} + {{ .Title }} + {{ end }} +
+ +
+ {{ .Content }} +
+ + {{ if .Params.tags }} + + {{ end }} +
+{{ end }} +``` + +### Create List Layout + +Create [`layouts/_default/list.html`](layouts/_default/list.html:1): + +```html +{{ define "main" }} +
+

{{ .Title }}

+ + {{ range .Pages }} +
+

{{ .Title }}

+ + {{ if .Params.excerpt }} +

{{ .Params.excerpt }}

+ {{ end }} + Read more → +
+ {{ end }} +
+{{ 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 +
+ +
+
+ + +``` + +### Add RSS Feed + +Create [`layouts/index.rss.xml`](layouts/index.rss.xml:1): + +```xml + + + {{ .Site.Title }} + {{ .Site.BaseURL }} + {{ .Site.Params.description }} + {{ .Site.LanguageCode }} + {{ range first 15 .Pages }} + + {{ .Title }} + {{ .Permalink }} + {{ .Params.excerpt }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }} + {{ .Permalink }} + + {{ end }} + + +``` + +## 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 \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..27d8d95 --- /dev/null +++ b/docs/package.json @@ -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" + } +} \ No newline at end of file diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..09c3547 --- /dev/null +++ b/hugo.toml @@ -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" diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..f65c8d1 --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,20 @@ +
+

{{ .Title }}

+ + {{ range .Pages }} +
+

{{ .Title }}

+ + {{ if .Params.excerpt }} +

{{ .Params.excerpt }}

+ {{ end }} + Read more → +
+ {{ end }} +
+{{ end }} \ No newline at end of file diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..6de429e --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,35 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+ + {{ if .Params.featured_image }} + {{ .Title }} + {{ end }} +
+ +
+ {{ .Content }} +
+ + {{ if .Params.tags }} + + {{ end }} +
+{{ end }} \ No newline at end of file diff --git a/scripts/fetch-wordpress.js b/scripts/fetch-wordpress.js new file mode 100644 index 0000000..41e1712 --- /dev/null +++ b/scripts/fetch-wordpress.js @@ -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); \ No newline at end of file diff --git a/scripts/generate-content.js b/scripts/generate-content.js new file mode 100644 index 0000000..b63e117 --- /dev/null +++ b/scripts/generate-content.js @@ -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(); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +