হুগো + GitHub + Decap CMS + Cloudflare Pages স্ট্যাক দিয়ে সার্ভারলেস, নিজস্ব ডোমেইন ও CMS সমৃদ্ধ ব্লগ তৈরি
দীর্ঘদিন ধরে
https://www.ixam.net
ডোমেইনে গুগল সাইটস (Google Apps যুগের) দিয়ে সাইট চালাতাম। অনেক আগেই গুগল সাইটস অকার্যকর হয়ে যাওয়ায় ডোমেইনটা ফাঁকা পড়ে ছিল। অবশেষে আবার কাজে লাগানোর সিদ্ধান্ত নিয়ে রিনিউ করার প্রক্রিয়ায় নামলাম। সার্ভারলেস রাখতে চাই, আর কনটেন্ট যেন গুগল সাইটসের মতো ইচ্ছেমতো বন্ধ হয়ে যাওয়া সেবার ওপর নির্ভর না করে—এই শর্ত নিয়ে ChatGPT-কে সঙ্গী করলাম।ভেবেছিলাম ChatGPT-ই সবকিছু সহজ করে দেবে। বাস্তবে দেখা গেল, মৌলিক প্রয়োজন বাদ পড়ে যায়, ট্রাবলশুটিং ঘুরপাক খায়—মোটেও সোজা নয়। শেষমেশ গুগল করে পাওয়া https://github.com/patrickgrey/website/ রেপোজিটরির মতো উদাহরণও দেখে, একসঙ্গে সমাধান খুঁজেছি।
যাকে বলে “টেকনিক্যালি দক্ষ, কিন্তু বারবার ভুল ধারণা করা জুনিয়রকে পথ দেখানো”—সে অভিজ্ঞতা। সেই পর্যায়ে পৌঁছে যাওয়ার মত এআই তৈরি হয়েছে, ভাবতেই অবাক লাগে।
তবে জটিলতা বাড়তে থাকলে সেই এআই-ও বেশ বিরক্তিকর ভুল করতে শুরু করে। এটাও একধরনের বাস্তবতা!?
আপাতত দীর্ঘ আলাপ চালিয়ে সব প্রাক-শর্ত বুঝে নেওয়ার বদলে, কথোপকথন জটিল হলেই মানুষ নিজে সংক্ষেপ করে নতুন থ্রেডে নির্দেশনা দিলে সবচেয়ে কার্যকর মনে হয়েছে।
তা সত্ত্বেও, একা করলে গবেষণা, কাজের পরিমাণ, ধৈর্য—কোনোটাই সামলাতে পারতাম না। জেনারেটিভ এআইয়ের উৎপাদনশীলতা সত্যিই বিস্ময়কর।
লক্ষ্য
https://www.example.com/
-এ ব্লগ প্রদর্শন করা যাবেhttps://www.example.com/admin/
থেকে DecapCMS-এ লগইন করে নিবন্ধ তৈরি করা যাবে- লেখা GitHub-এ কমিট হবে এবং Cloudflare স্বয়ংক্রিয়ভাবে ডিপ্লয় করবে
- বর্তমানে অতিরিক্ত কোনো রানিং কস্ট নেই (ডোমেইন রেজিস্ট্রেশনের পুরোনো খরচ ছাড়া)
0. পূর্বশর্ত ও ফোল্ডার বিন্যাস
0-1. পূর্বশর্ত
-
ডোমেইন (
example.com
) ইতোমধ্যে ক্রয় করা আছে -
OS Windows / macOS যেকোনোটি হতে পারে (উভয়ের কমান্ড উদাহরণ দেওয়া আছে)
-
যেসব সেবা লাগবে (ফ্রি টিয়ার যথেষ্ট)
- GitHub অ্যাকাউন্ট
- Cloudflare অ্যাকাউন্ট (Pages ব্যবহার)
- লোকাল Git
- Hugo (লোকাল প্রিভিউ ও Cloudflare বিল্ডে ব্যবহৃত)
0-2. রেপোজিটরির প্রধান কাঠামো (উদাহরণ)
hugo.toml
content/
blog/
posts/
data/
functions/
api/
auth.js
callback.js
layouts/
_default/
baseof.html
index.html
list.html
single.html
static/
_headers
admin/
config.yml
index.html
css/
main.css
এই কাঠামোর ভূমিকা নিম্নরূপঃ
hugo.toml
… সাইটের সার্বিক সেটিং ( baseURL অবশ্যই প্রোডাকশন URL দিয়ে বদলাতে হবে )functions/api/*.js
… Cloudflare Pages Functions (GitHub OAuth এর/api/auth
ও/api/callback
)layouts/_default/*.html
… Hugo টেমপ্লেটstatic/admin/
… DecapCMS এর UI ও কনফিগারেশন ফাইলstatic/css/main.css
… ভিজ্যুয়াল (CSS)static/_headers
… Cloudflare Pages-এর HTTP হেডার সেটিং (ঐচ্ছিক)
※ এই নির্দেশিকায় পরিবেশভেদে বদলানোর জায়গাগুলোকে “প্রতিস্থাপন চেক” হিসেবে আলাদা করে দেখানো হয়েছে।
1. পূর্ব প্রস্তুতি
1-1. GitHub অ্যাকাউন্ট তৈরি
- ব্রাউজার থেকে GitHub-এ গিয়ে সাইন আপ করুন
1-2. Cloudflare অ্যাকাউন্ট তৈরি
- ব্রাউজার থেকে Cloudflare-এ গিয়ে সাইন আপ করুন
1-3. Git ও Hugo ইনস্টল করা
-
Windows (PowerShell)
PS C:\Users\alice> winget install Git.Git PS C:\Users\alice> winget install Hugo.Hugo.Extended
-
macOS (Terminal)
mac:~ dev$ brew install git mac:~ dev$ brew install hugo
2. মূল ধাপ (ডিপ্লয় পর্যন্ত)
2-1. লোকালে রেপোজিটরি প্রস্তুত করা
-
GitHub-এ একটি খালি রেপোজিটরি তৈরি করুন
- নেভিগেশন: GitHub > New repository
- নাম উদাহরণ:
my-hugo-blog
-
লোকালে ক্লোন করুন
-
Windows
PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git PS C:\work> cd .\my-hugo-blog
-
macOS
mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git mac:~/work dev$ cd my-hugo-blog
-
-
উপরের ফোল্ডার ও ফাইলসমূহ এই রেপোজিটরির রুটে কপি করুন
প্রতিস্থাপন চেক (এখানে অবশ্যই সংশোধন করবেন)
-
hugo.toml
baseURL = "https://www.example.com" # ← প্রোডাকশন URL (উদাহরণ: https://www.example.com) languageCode = "ja-jp" title = "Example Blog" publishDir = "public" [permalinks] blog = "/blog/:year/:month/:slug/"
-
static/admin/config.yml
backend: name: github repo: <YOUR_GH_USERNAME>/my-hugo-blog # ← নিজের GitHub রেপোজিটরি branch: master # ← ডিফল্ট ব্রাঞ্চ নাম (main অথবা master) base_url: https://www.example.com # ← প্রোডাকশন URL auth_endpoint: /api/auth # ← Functions এন্ডপয়েন্ট (নির্দিষ্ট) media_folder: static/uploads public_folder: /uploads
টিপস:
functions/api/auth.js
এবংfunctions/api/callback.js
그대로 রেখে দিতে পারেন (এগুলোurl.origin
ব্যবহার করে, তাই পরিবেশভেদে হার্ডকোড নেই)।
-
প্রথম কমিট ও পুশ
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages" PS C:\work\my-hugo-blog> git push -u origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages" mac:~/work/my-hugo-blog dev$ git push -u origin master
-
2-2. GitHub OAuth App তৈরি করা (DecapCMS লগইনের জন্য)
-
নেভিগেশন: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
ইনপুট:
- Application name:
[DecapCMS](https://decapcms.org/) for my-hugo-blog
- Homepage URL:
https://www.example.com
- Authorization callback URL:
https://www.example.com/api/callback
← অত্যন্ত গুরুত্বপূর্ণ
- Application name:
-
তৈরি হলে পাওয়া যাবে:
- Client ID
- Client Secret (নতুন করে জেনারেট করে রেখে দিন)
এগুলো Cloudflare Pages-এর এনভায়রনমেন্ট ভেরিয়েবল হিসেবে সেট করবেন।
2-3. Cloudflare Pages প্রোজেক্ট তৈরি
-
নেভিগেশন: Cloudflare ড্যাশবোর্ড > Pages > Create a project > Connect to Git
-
GitHub সংযোগ করুন এবং
my-hugo-blog
রেপোজিটরি নির্বাচন করুন -
বিল্ড সেটিং:
-
Framework preset:
None
(Cloudflare চাইলে স্বয়ংক্রিয় সনাক্ত করবে) -
Build command:
hugo
-
Build output directory:
public
-
এনভায়রনমেন্ট ভেরিয়েবল:
HUGO_VERSION
=0.128.0
(উদাহরণ: লোকাল সংস্করণের সাথে মিল রাখলে সুবিধা)GITHUB_CLIENT_ID
= (পূর্ববর্তী ধাপে প্রাপ্ত)GITHUB_CLIENT_SECRET
= (পূর্ববর্তী ধাপে প্রাপ্ত)
-
-
Save and Deploy চাপুন এবং প্রথম ডিপ্লয় সম্পন্ন হওয়া পর্যন্ত অপেক্ষা করুন
- সাফল্য হলে একটি
*.pages.dev
প্রিভিউ URL পাওয়া যাবে
- সাফল্য হলে একটি
2-4. নিজস্ব ডোমেইন www.example.com
যোগ করা
-
নেভিগেশন: Cloudflare > Pages > (প্রাসঙ্গিক প্রোজেক্ট) > Custom domains > Set up a custom domain
-
www.example.com
লিখে যোগ করুন -
যদি ডোমেইন Cloudflare-এ স্থানান্তরিত না থাকে:
- Cloudflare-এ দেখানো DNS রেকর্ড তথ্য (উদাহরণ:
CNAME www -> <project>.pages.dev
) নোট করুন - যেই রেজিস্ট্রারে ডোমেইন ম্যানেজ হয়, তাদের নির্দেশনা মেনে সেই রেকর্ড যোগ করুন
- Cloudflare-এ দেখানো DNS রেকর্ড তথ্য (উদাহরণ:
-
DNS সক্রিয় হলে
https://www.example.com/
খুলে দেখা যাবে
হিন্ট: ডোমেইন Cloudflare-এ থাকলে বোতাম ক্লিকেই প্রয়োজনীয় DNS স্বয়ংক্রিয়ভাবে যুক্ত হয়।
2-5. DecapCMS অ্যাডমিন প্যানেলে লগইন
- ব্রাউজারে
https://www.example.com/admin/
খুলুন - “Login with GitHub” ধরনের বোতাম থেকে GitHub OAuth অনুমোদন দিন
- প্রথমবার GitHub “এই অ্যাপ অনুমোদন করবেন?” জিজ্ঞেস করলে Authorize চাপুন
লগইন না হলে OAuth Callback URL এবং Cloudflare ভেরিয়েবল (
GITHUB_CLIENT_ID/SECRET
) আবার যাচাই করুন।
3. অপারেশন গাইড (নিবন্ধ লেখা, লোকাল সিঙ্ক, টেমপ্লেট সম্পাদনা)
3-1. CMS থেকে নিবন্ধ তৈরি
https://www.example.com/admin/
খুলুন- বাম ন্যাভ থেকে Blog > New blog নির্বাচন করুন
Title
,Publish Date
,Description
,Body
ইনপুট দিন- Publish করলে GitHub-এ স্বয়ংক্রিয় কমিট → Cloudflare স্বয়ংক্রিয় ডিপ্লয়
উৎপন্ন Markdown ফাইল
content/blog/
-এ সংরক্ষণ হয়।
3-2. CMS দিয়ে পোস্টের পর লোকাল রেপোজিটরি আপডেট
-
Windows
PS C:\work\my-hugo-blog> git pull origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git pull origin master
এতে লোকালে সর্বশেষ পোস্ট নেমে আসবে এবং টেমপ্লেট/CSS নিরাপদে সম্পাদনা করা যাবে।
3-3. লোকালে ডিজাইন বা টেমপ্লেট এডিট
-
লোকাল ডেভ সার্ভার চালু করুন
-
Windows
PS C:\work\my-hugo-blog> hugo server -D
-
macOS
mac:~/work/my-hugo-blog dev$ hugo server -D
-
ব্রাউজারে
http://localhost:1313/
খুলে প্রিভিউ দেখুন
-
-
পরিবর্তনের উদাহরণ
layouts/_default/baseof.html
…<head>
ও হেডার/ফুটার অংশlayouts/_default/index.html
… হোমপেজের “সর্বশেষ নিবন্ধ” তালিকাlayouts/_default/single.html
… নিবন্ধের মূল টেমপ্লেটstatic/css/main.css
… রং, ফন্ট, মার্জিন ইত্যাদি
-
পরিবর্তন কমিট ও পুশ করুন
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts" PS C:\work\my-hugo-blog> git push
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts" mac:~/work/my-hugo-blog dev$ git push
-
কয়েক সেকেন্ড/মিনিটের মধ্যে Cloudflare স্বয়ংক্রিয়ভাবে ডিপ্লয় করে দেবে
-
3-4. ব্রাঞ্চ ব্যবস্থাপনা টিপস
- ডিফল্ট ব্রাঞ্চ
main
নাকিmaster
—এই নামটি DecapCMS এরconfig.yml
ও Cloudflare সেটিং এ একই রাখুন - প্রিভিউ দরকার হলে GitHub-এ Pull Request খুললেই Cloudflare Pages Preview পরিবেশ জেনারেট করে
4. অতিরিক্ত সুবিধা (ঐচ্ছিক)
4-1. static/_headers
উদাহরণ (অ্যাডমিন প্যানেল ক্যাশ না করা)
/admin/*
Cache-Control: no-store
4-2. robots ও সাইটম্যাপ (প্রয়োজনে)
static/robots.txt
দিয়ে ক্রলার নিয়ন্ত্রণhugo.toml
-এoutputs
যোগ করে RSS বা সাইটম্যাপ আউটপুট বাড়ানো সম্ভব
5. সাধারণ সমস্যাগুলো ও সমাধান
- CMS লগইন ঘুরেই চলেছে
→ GitHub OAuth-এর Callback URLhttps://www.example.com/api/callback
কি না এবং Cloudflare ভেরিয়েবলGITHUB_CLIENT_ID/SECRET
সঠিক কি না দেখুন - হোমপেজ খুলছে কিন্তু নিবন্ধে 404
→hugo.toml
-এরbaseURL
প্রকৃত ডোমেইনের সঙ্গে মিলে কি না ও Cloudflare বিল্ড লগেpublic/
তৈরি হচ্ছে কি না চেক করুন - /admin একেবারে ফাঁকা
→static/admin/index.html
-এ DecapCMS লোডিং (CDN) ব্লক হচ্ছে কি না, ব্রাউজার এক্সটেনশন বন্ধ করে পুনরায় চেষ্টা করুন
6. প্রতিস্থাপন ও সেটিং চেক—সংক্ষিপ্ত তালিকা
ফাইল/সেটিং | কী | প্রতিস্থাপন/সেটিং উদাহরণ |
---|---|---|
hugo.toml |
baseURL |
https://www.example.com |
static/admin/config.yml |
repo |
<YOUR_GH_USERNAME>/my-hugo-blog |
static/admin/config.yml |
branch |
বাস্তব অনুযায়ী main বা master |
static/admin/config.yml |
base_url |
https://www.example.com |
Cloudflare Pages | HUGO_VERSION |
উদাহরণ: 0.128.0 |
Cloudflare Pages | GITHUB_CLIENT_ID |
GitHub OAuth App-এর Client ID |
Cloudflare Pages | GITHUB_CLIENT_SECRET |
GitHub OAuth App-এর Client Secret |
GitHub OAuth App | Callback URL | https://www.example.com/api/callback |
Cloudflare Pages | Custom domain | www.example.com যোগ করুন |
7. সংযোজন: নমুনা ফাইল (প্রয়োজনমতো প্রতিস্থাপন করে ব্যবহার করুন)
7-1. functions/api/auth.js
// Cloudflare Pages Functions (/api/auth)
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
try {
const url = new URL(request.url);
const redirectUrl = new URL('https://github.com/login/oauth/authorize');
redirectUrl.searchParams.set('client_id', client_id);
redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
redirectUrl.searchParams.set('scope', 'repo user');
redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
return Response.redirect(redirectUrl.href, 302);
} catch (err) {
return new Response(String(err?.message || err), { status: 500 });
}
}
7-2. functions/api/callback.js
function renderBody(status, content) {
const html = `
<script>
const receiveMessage = (message) => {
window.opener.postMessage(
'authorization:github:${status}:${JSON.stringify(content)}',
message.origin
);
window.removeEventListener("message", receiveMessage, false);
}
window.addEventListener("message", receiveMessage, false);
window.opener.postMessage("authorizing:github", "*");
</script>`;
return new Blob([html]);
}
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
const client_secret = env.GITHUB_CLIENT_SECRET;
try {
const url = new URL(request.url);
const code = url.searchParams.get('code');
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
body: JSON.stringify({ client_id, client_secret, code }),
});
const result = await response.json();
if (result.error) {
return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
}
const token = result.access_token;
const provider = 'github';
return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
} catch (error) {
return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
}
}
7-3. static/admin/config.yml
backend:
name: github
repo: <YOUR_GH_USERNAME>/my-hugo-blog
branch: main
base_url: https://www.example.com
auth_endpoint: /api/auth
media_folder: static/uploads
public_folder: /uploads
collections:
- name: 'blog'
label: 'Blog'
folder: 'content/blog'
create: true
slug: '{{slug}}'
editor:
preview: false
fields:
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Publish Date', name: 'date', widget: 'datetime' }
- { label: 'Description', name: 'description', widget: 'string' }
- { label: 'Body', name: 'body', widget: 'markdown' }
9. SEO ও পারফরম্যান্স উন্নতির পয়েন্ট
ব্লগটি সেটআপের পর নিচের পদক্ষেপগুলো করলে সার্চ র্যাংক ও গতি আরও উন্নত হবে।
-
সাইটম্যাপ ও robots.txt সেটিং
- Hugo স্বয়ংক্রিয়ভাবে তৈরি করা
public/sitemap.xml
Google Search Console-এ জমা দিন static/robots.txt
-এ ক্রলার নিয়ন্ত্রণ যুক্ত করুন
- Hugo স্বয়ংক্রিয়ভাবে তৈরি করা
-
OGP (Open Graph Protocol) ও টুইটার কার্ড
layouts/_default/baseof.html
-এ<meta property="og:title">
,<meta property="og:image">
ইত্যাদি যোগ করুন- সোশ্যাল শেয়ারে আকর্ষণীয় প্রদর্শনের জন্য কার্যকর
-
ছবিকে WebP-তে রূপান্তর
resources/_gen/images/
ব্যবহার করে Hugo পাইপলাইনে স্বয়ংক্রিয় WebP তৈরি করুন- পেজ লোডিং সময় কমে যাবে
-
ক্যাটাগরি/ট্যাগ ফিচার বাড়ানো
content/blog/
-এর ফ্রন্ট ম্যাটারেcategories
ওtags
যোগ করে টেমপ্লেটে ট্যাগ ক্লাউড দেখাতে পারেন
-
স্ট্রাকচার্ড ডেটা
- JSON-LD ফরম্যাটে নিবন্ধ তথ্য সার্চ ইঞ্জিনকে জানিয়ে রিচ রেজাল্টের সম্ভাবনা তৈরি করুন
8. সারসংক্ষেপ
- GitHub-এ রেপোজিটরি, Cloudflare Pages-এ সংযোগ, DecapCMS এর GitHub OAuth সেটিং, নিজস্ব ডোমেইন ম্যাপ করলেই বেসিক সেটআপ সম্পন্ন
/admin/
থেকে নিবন্ধ তৈরি → GitHub-এ কমিট → Cloudflare স্বয়ংক্রিয় ডিপ্লয়- টেমপ্লেট ও ডিজাইন লোকালে
hugo server
চালিয়ে এডিট করুন, তারপর Git দিয়ে প্রতিফলিত করুন
এভাবে Hugo + GitHub + Decap CMS + Cloudflare ভিত্তিক সার্ভারলেস ব্লগ সহজে তৈরি ও রক্ষণাবেক্ষণ সম্ভব।
এখনো পর্যন্ত স্বল্প খরচে একটি সাইট দাঁড়াল বটে, কিন্তু সাইট সার্চ, ক্যাটাগরি, ট্যাগ ও ট্যাগ ক্লাউড—এসব তুলনা করলে পরিণত ব্লগের মতো সমৃদ্ধ নয়। আপাতত এটা মেনে নিয়েছি; ধীরে ধীরে ChatGPT-কে সঙ্গে নিয়ে সমৃদ্ধ করবো।