Chuyển website từ WordPress sang KDIGI CMS
Cập nhật lần cuối:

KDIGI CMS chuyển nội dung từ bất kỳ site WordPress nào bằng một pipeline 8 phase có thể chạy lại: tải bản chụp qua REST hoặc WPGraphQL, chuẩn hóa thành JSON để bạn duyệt, khóa cổng review rồi mới ghi vào CMS. Bài viết, trang, media, menu, SEO Yoast/RankMath và redirect 301 được mang sang.
Tóm tắt nhanh
- Dữ liệu đi theo đường
WordPress → raw/ (bản sao bất biến) → staged/ (JSON sửa được) → cổng review → Payload.- Không có gì được ghi vào database trước khi bạn khóa dữ liệu bằng
--gate lock.- Database không phải local luôn được
pg_dumptrước khi ghi, không có cờ bỏ qua.- Yoast và RankMath đều được đọc và quy về một schema SEO thống nhất; JSON-LD do CMS sinh lại.
- Mọi lệnh idempotent: chạy lại không tạo bản trùng, có
--dry-runđể xem trước.- Không di chuyển theme, plugin, form, tài khoản đăng nhập WordPress và dữ liệu page builder lưu ngoài
post_content.
Trang này tóm tắt cách pipeline hoạt động, những gì được mang sang, những gì phải làm tay và checklist nghiệm thu. Lệnh chi tiết từng bước nằm trong runbook trên GitHub; trang Thực hành tốt gom các quy tắc SEO nên áp dụng khi cutover.

Những gì được di chuyển
Bảng dưới đây lấy từ tài liệu pipeline của KDIGI CMS (phạm vi di chuyển, bản 1.1.0).
| Nguồn WordPress | Đích KDIGI CMS | Ghi chú |
|---|---|---|
post |
posts |
Nội dung chuyển sang Markdown (contentMd), giữ ngày xuất bản |
page |
pages |
Trang lồng nhau được làm phẳng theo slug cuối, kèm redirect từ URL cũ |
category, post_tag |
taxonomies (category, tag) |
Giữ cây cha/con |
| Taxonomy tùy biến | taxonomies (custom + customType) |
customType là tên taxonomy gốc |
WooCommerce product, product_cat, product_tag |
products, taxonomies |
Tùy chọn; một giá chung, gallery thành slider |
| Custom post type khác | posts, pages hoặc products |
Khai báo bằng --cpt <type>=<collection> |
| Media Library | media |
Tải binary qua REST, lưu local-disk hoặc R2 theo cấu hình |
| Menu | menus |
Cây nhiều cấp, map vị trí về PRIMARY, FOOTER_MENU… |
| Tác giả | authors |
Lấy từ bài viết, thêm /users nếu REST cho phép |
| Bình luận | comments |
Bình luận spam giữ trạng thái spam |
| Yoast SEO / RankMath | seo.* của từng tài liệu |
Title, description, canonical, robots, Open Graph, twitter, focus keyword |
| Logo, favicon, JSON-LD Organization, mạng xã hội | Cấu hình site | Chỉ điền phần WordPress thực sự sở hữu |
| Permalink cũ | redirects (301) |
Tự sinh khi đường dẫn đổi; thêm tay qua staged/redirects/ |
Không di chuyển: theme và giao diện, plugin, cấu hình form, người dùng đăng nhập WordPress, widget/sidebar, custom fields ACF/meta box, redirect nằm trong plugin (RankMath Redirections, Yoast Premium), sitemap và robots.txt (frontend của bạn sinh lại).
Pipeline hoạt động thế nào
Pipeline gồm bốn bước lớn, điều phối bởi src/importers/run-wp-migration.ts:
- Archive. Tải bản chụp site nguồn. Ưu tiên WPGraphQL nếu site có; nếu không, dựng từ REST API chuẩn của WordPress nên chạy được trên mọi site. Thư mục
raw/là bản sao nguồn, không sửa tay. - Normalize. Chuyển
raw/thànhstaged/: HTML thành Markdown, SEO Yoast/RankMath thành một schema thống nhất, cùng taxonomy, menu, tác giả, media và bình luận. Bước này không ghi database và không gọi mạng. - Review gate. Con người rà soát và sửa các file JSON trong
staged/.--gate lintphải sạch lỗi,--gate lockghi hash nội dung vào.gate-lock. Sửastaged/sau khi khóa sẽ làm khóa mất hiệu lực. - Import. Ghi vào Payload, mỗi phase kiểm lại khóa trước khi chạy.
8 phase ghi dữ liệu
Phase schema chuẩn bị database (chạy pnpm payload migrate và kiểm các giá trị enum cần thiết). Tám phase ghi dữ liệu là seed đến redirects:
| # | Phase | Việc làm |
|---|---|---|
| 1 | seed |
Tạo site nếu chưa có (không bao giờ ghi đè site sẵn có), tạo tác giả |
| 2 | taxonomy |
Upsert category, tag, product-category, custom; nối cây cha/con |
| 3 | content |
Upsert pages, posts, products kèm FAQ, rating, giá; giữ publishedAt của WordPress |
| 4 | media |
Tải/tạo media, gắn ảnh đại diện, gallery, ảnh OG và ghi lại URL ảnh trong thân bài |
| 5 | seo |
Áp lại nhóm seo: canonical, robots, OG, twitter, ngày bài viết |
| 6 | nav |
Menu, cấu hình site (logo, favicon, knowledge graph, social), FAQ |
| 7 | comments |
Bình luận của post/product |
| 8 | redirects |
301 từ permalink cũ sang đường dẫn mới, cộng các dòng thủ công |
6 giai đoạn từ đầu đến cutover
- Baseline nguồn. Lưu sitemap, canonical và status code của toàn bộ URL WordPress hiện tại; đóng băng nội dung từ lúc archive cuối đến cutover.
- Archive. Tải bản chụp REST hoặc WPGraphQL vào
wp-archive/<site>/raw/(kèm binary media). - Normalize. Sinh
staged/gồm JSON mỗi tài liệu, SEO đã thống nhất, taxonomy, menu, tác giả, media, bình luận. - Review và khóa. Sửa tay
staged/,--gate lintsạch rồi--gate lock. - Import.
--dry-runtrước, sau đó--from schema --to redirectsvới sao lưupg_dumpbắt buộc khi database không phải local. - Nghiệm thu và cutover. Duyệt checklist, so sánh lại URL, chuyển DNS, theo dõi Search Console 2-4 tuần rồi mới tắt WordPress.
Cảnh báo KDIGI CMS không phải bản sao WordPress: theme, plugin, form, ACF/meta box và layout của page builder không được chuyển. Phần nào cần giữ hãy dựng lại ở frontend hoặc bằng collection
landings.
Chạy di chuyển từng bước
Dưới đây là luồng rút gọn từ runbook, chạy ở thư mục gốc của repo kdigi-cms-open (Node ≥ 20, pnpm, PostgreSQL đích đã migrate, DATABASE_URI trong .env).

1. Đặt biến và kiểm tra nguồn
SITE=my-site # siteKey: a-z, 0-9, dấu -
WP_URL=https://wp.example.com # backend WordPress (REST/GraphQL)
DOMAIN=example.com # domain công khai MỚI trên KDIGI CMS
mig() { node --env-file=.env --import tsx src/importers/run-wp-migration.ts \
--site "$SITE" --wp-url "$WP_URL" --domain "$DOMAIN" "$@"; }
curl -s -o /dev/null -w '%{http_code}\n' "$WP_URL/wp-json/wp/v2/posts?per_page=1" # mong đợi 200
Nếu REST trả 401/403 cho mọi thứ, thường là plugin bảo mật hoặc WAF đang chặn; tạo Application Password và đặt WP_AUTH_USER, WP_AUTH_PASSWORD.
2. Archive và normalize
mig # lần đầu: archive → normalize → dừng ở review gate (exit khác 0 là bình thường)
Đặt WP_UTC_OFFSET=+07:00 (hoặc múi giờ đúng của WordPress) trước khi normalize để publishedAt không lệch giờ; không đặt thì giá trị được coi là UTC. Kết quả nằm trong wp-archive/<site>/raw/ và wp-archive/<site>/staged/. Thư mục wp-archive/ đã nằm trong .gitignore, đừng commit dữ liệu WordPress.
3. Review, lint, lock
mig --gate lint # lỗi (chặn lock) và cảnh báo (không chặn)
mig --gate lock # lint sạch thì ghi staged/.gate-lock {approver, timestamp, contentHash}
mig --gate status # khóa còn hiệu lực không
Tối thiểu hãy rà các mục sau trong staged/:
contentMdcủa vài bài mẫu mỗi loại: heading, bảng, ảnh, shortcode còn sót dạng[xyz].seo.canonicalphải trỏ về domain mới, không phải host WordPress. Lint báo lỗi nếu còn trỏ host cũ.- Cây taxonomy và menu; term thừa xóa bằng file
.delete-<tên>.json. - Alt của media (xem
media-alt-fallback-report.md); ảnh nhạy cảm đưa vàodenylist.json.

4. Dry-run rồi import thật
mig --from schema --to redirects --dry-run # xem trước, không ghi Payload
mig --from schema --to redirects # import thật
Dry-run vẫn kết nối database để kiểm enum. Với lần ghi thật vào database không phải local, CMS hỏi xác nhận rồi chạy pg_dump -Fc vào backups-local/. Hãy ghi lại tên file dump, đó là điểm rollback. Bạn cần pg_dump (gói postgresql-client) khớp phiên bản server.
Dùng KDIGI CMS từ phiên bản 1.0.1 trở lên. Bản 1.0.1 sửa lỗi phase
mediathiếu khóa idempotencysourceMediaIdkhiến ảnh đại diện, slider và ảnh trong thân bài không được nhập. Bản hiện hành là 1.1.0, xem lịch sử phát hành.
5. Kiểm tra sau import
| Phase | Dấu hiệu đúng |
|---|---|
schema |
Không có lỗi schema gate: … enum missing |
seed |
Site $SITE xuất hiện trong admin với đúng domain |
taxonomy |
Log gate OK — counts match staged |
content |
errors: 0; chạy lại lần hai phải ra skipped: N |
media |
failed: 0; ảnh trong thân bài trỏ về …/api/media/file/… |
seo, nav, comments, redirects |
errors: 0 |
Sau khi site mới đã có dữ liệu, bạn cấu hình theme, footer, thông tin liên hệ và SEO mặc định trong admin ở mục Sites. Xem thêm quản lý nhiều website.
Quyết định theo từng loại site WordPress
Yoast SEO hay RankMath
Cả hai được quy về cùng một cấu trúc: title, description, canonicalOverride, robots, focusKeywords, Open Graph, twitter meta và ngày xuất bản/sửa. JSON-LD không được nhập mà được CMS sinh lại từ dữ liệu tài liệu và knowledge graph của site (SEO và JSON-LD).
| Yoast SEO | RankMath | |
|---|---|---|
| Qua REST | Có sẵn yoast_head_json trên mọi post, page, term |
Dựng từ trường rank_math_*; nếu thiếu, gọi /wp-json/rankmath/v1/getHead cho từng URL (có throttle) |
| Qua WPGraphQL | Cần plugin WPGraphQL Yoast SEO | Cần plugin WPGraphQL for Rank Math |
| Focus keyword | Yoast không công khai qua REST nên để trống | Có |
Một lưu ý hay bị bỏ sót: nhiều site WordPress đặt canonical tùy biến trong database mà không phát ra ở frontend. Nếu mang nguyên sang, CMS sẽ áp dụng và có thể de-index nhầm trang đích. Trước khi lock, hãy curl trang thật và so với seo.canonicalOverride trong staged/.
Gutenberg, Classic hay page builder
Importer chỉ đọc HTML đã render của post_content rồi chuyển thành GitHub-flavoured Markdown.
| Cách soạn | Kết quả | Việc cần làm |
|---|---|---|
| Classic Editor | Tốt nhất: đoạn, heading, list, bảng, ảnh, link | Kiểm shortcode còn sót |
| Gutenberg | Tốt: bỏ chú thích <!-- wp:… -->, chuyển được image, bảng, list, quote, gallery, embed |
Block động (query loop, form) không có trong post_content nên mất |
| Elementor, Divi, WPBakery | Cấu trúc bị làm phẳng; layout cột, section, animation mất | Xem lại từng trang quan trọng; dựng lại landing bằng collection landings của CMS |
| Shortcode | [gallery] thành bảng ảnh; shortcode lạ bị loại, nội dung bao quanh được giữ |
Form, slider, popup dựng lại thủ công |
Trang Elementor hay Divi bị rỗng thường do nội dung nằm trong meta (_elementor_data) còn post_content gần như trống; --gate lint sẽ cảnh báo contentMd empty.
WooCommerce
Tùy chọn. Sản phẩm trở thành Products của KDIGI CMS (trang sản phẩm/dịch vụ) với một mức giá, tiền tệ trong VND, USD, EUR, GBP, JPY. Biến thể, thuộc tính, tồn kho, giỏ hàng, thanh toán, đơn hàng, coupon và khách hàng không chuyển. Nếu bạn cần bán hàng online, KDIGI CMS không thay thế WooCommerce: hãy giữ WooCommerce làm backend bán hàng và chỉ dùng pipeline để chuyển nội dung và SEO. Với website chỉ giới thiệu catalog, Products là đủ; xem thêm giải pháp website bán hàng.
Thư viện media lớn
- Script archive tải song song (
--concurrency=N, mặc định 8), bỏ qua file đã có và ghi file lỗi vàoraw/media/failed.json; bị ngắt thì chạy lại để tải tiếp. - Phase
mediatạo một media cho mỗi ảnh gốc trong inventory, không phải từng kích thước thumbnail, và nhận lại ảnh đã upload quasourceMediaId. - Cấu hình storage (local-disk hoặc Cloudflare R2) trước khi import để file đi thẳng vào nơi cuối cùng, xem thư viện media.
- Đặt
PAYLOAD_PUBLIC_SERVER_URLvề URL thật trước phasemedia, nếu không URL localhost sẽ bị ghi vào thân bài (sửa bằng cách chạy lại--from content --to media).
Redirect và cutover
Phase redirects sinh 301 từ permalink WordPress (ví dụ /2024/05/ten-bai/) sang đường dẫn mới cho mọi tài liệu đã published. Quy tắc cần nhớ:
- Không tự sinh redirect cho trang lưu trữ taxonomy (
/category/<slug>/,/tag/<slug>/), trang tác giả, phân trang, feed,?p=123và attachment. Lấy danh sách URL có traffic/backlink từ Search Console hoặc log, rồi thêm vàostaged/redirects/*.jsonvớistatusCode301 hoặc 302. - Không tạo chuỗi A → B → C;
toluôn là đích cuối. - Nội dung đã xóa nên để
draft(trả 404) thay vì redirect về trang chủ, vì soft-404 gây hại SEO. - Thử vài URL cũ:
curl -sI https://example.com/2024/05/old-post/ | grep -iE '^(HTTP|location)'.
Ba kịch bản cutover được tài liệu mô tả:
- Đổi domain:
--wp-url https://old.example.com --domain example.com. Canonical vàog:urlđược viết lại sang domain mới, link nội bộ trong bài trở thành đường dẫn tương đối. - Backend WordPress tách khỏi site công khai: thêm
--public-url https://example.comđể link nội bộ trỏ về đường dẫn tương đối. - Thay thế tại chỗ (cùng host): bắt buộc có redirect cho mọi URL thay đổi và kiểm robots/canonical trên bản staging trước khi chuyển DNS.
Quy trình khuyến nghị: baseline site nguồn (sitemap, canonical, status code toàn bộ URL) → import vào database local/staging → duyệt checklist → import production (có backup) → so sánh lại URL → chuyển DNS → theo dõi Search Console 2-4 tuần → tắt WordPress. Giữ WordPress chạy ít nhất 2-4 tuần sau cutover.
Checklist nghiệm thu
- Số lượng posts, pages, products, taxonomies, media, menus, comments trong CMS khớp
staged/(trừ phần cố ý bỏ). - Chạy lại toàn bộ
--from schema --to redirectskhông làm đổi số tài liệu, logcreated: 0. - 10 URL ngẫu nhiên cùng toàn bộ URL trong sitemap WordPress cũ mở được hoặc redirect 301 đúng đích.
- Thân bài không còn Markdown thô,
[shortcode]hay chữ dính nhau; bảng và ảnh hiển thị đủ. - Canonical, OG, robots đúng domain mới; không có
noindexngoài ý muốn. - Ngày xuất bản và sửa đúng nguồn, không phải ngày import.
- Menu header, footer, mobile đúng; link nội bộ không 404.
- Logo, favicon, knowledge graph, social đã đúng; theme, footer, contact đã cấu hình trong admin.
- Đã lưu file
pg_dumpvà thư mụcwp-archive/<site>/(raw, staged,.gate-lock).
Rollback
| Tình huống | Cách quay lại |
|---|---|
Sai staged/, chưa import |
Sửa, lint, lock lại; không có gì để rollback |
| Import local/dev hỏng | pnpm migrate:down rồi pnpm migrate và import lại, hoặc xóa site trong admin và chạy lại |
| Import vào database không local hỏng | pg_restore --clean --if-exists --no-owner -d "$DATABASE_URI" backups-local/db-…-pre-wp-migration-$SITE.dump (dừng app trước) |
| Chỉ vài tài liệu sai | Dùng Versions của tài liệu để Restore, hoặc sửa staged/ và chạy lại đúng phase |
| Đã chuyển DNS nhưng lỗi nặng | Trỏ DNS về WordPress cũ, vì bạn đã giữ nó chạy |
Đừng xóa wp-archive/<site>/raw/ hay file dump trước khi cutover ổn định.
Sau khi chuyển xong
Bạn có một site KDIGI CMS để hiển thị nội dung qua REST và GraphQL API trên frontend tự chọn. Nếu muốn tự vận hành máy chủ, xem hướng dẫn tự host và sao lưu, nâng cấp. Muốn thử giao diện biên tập trước khi di chuyển thật, vào bản demo trực tiếp. Nếu còn cân nhắc giữa hai hệ thống, so sánh KDIGI CMS và WordPress nêu rõ khi nào nên giữ WordPress; sau cutover, áp dụng SEO on-page cho nội dung mới.
Nguồn
- Tài liệu di chuyển WordPress của KDIGI CMS (README, runbook, playbook): https://github.com/kdigivn/kdigi-cms-open/tree/main/docs/wordpress-migration
- Bản hosted trên docs: https://docs.opencms.kdigi.vn/wordpress/di-chuyen-tu-wordpress/
- CHANGELOG (1.0.1 sửa phase media): https://github.com/kdigivn/kdigi-cms-open/blob/main/CHANGELOG.md
Câu hỏi thường gặp
Chuyển từ WordPress sang KDIGI CMS có mất thứ hạng SEO không?
Những gì KDIGI CMS không di chuyển từ WordPress?
Tôi có cần tài khoản quản trị WordPress để di chuyển không?
Nếu import hỏng giữa chừng thì khôi phục thế nào?
Có chuyển được site WordPress đa ngôn ngữ (Polylang, WPML) không?
Đọc tiếp
Tự host
Cài KDIGI CMS trên VPS của bạn bằng Docker Compose hoặc Coolify: yêu cầu hệ thống, biến môi trường, sao lưu, nâng cấp và các bước tăng cường bảo mật.
SEO on-page với KDIGI CMS: checklist từng bước
Checklist SEO on-page thực hành trong KDIGI CMS: title, meta description, heading, canonical, internal link, JSON-LD, sitemap và kiểm tra SEO có sẵn.
KDIGI CMS vs WordPress: nên chọn cái nào?
So sánh KDIGI CMS và WordPress về kiến trúc, bảo mật, SEO, plugin, chi phí và công sức chuyển đổi. Có bảng, nguồn dẫn và khi nào nên ở lại WordPress.
SEO và JSON-LD
Trường SEO, mẫu title kiểu RankMath, đồ thị JSON-LD, ảnh OG dự phòng, kiểm tra SEO, gợi ý internal link và AI SEO tùy chọn ngay trong trình soạn thảo.