JobPosting schema trong Next.js: những lỗi Google Search Console hay báo và cách sửa
Khi làm một trang tuyển dụng, phần mang lại nhiều traffic nhất từ Google thường là Google for Jobs – khối hiển thị tin tuyển dụng ngay trên trang kết quả tìm kiếm. Để vào được khối này, mỗi trang tin phải có dữ liệu có cấu trúc JobPosting hợp lệ. Bài này tổng hợp các lỗi mình gặp thực tế khi vận hành một job board viết bằng Next.js (App Router) và cách sửa từng lỗi.
1. Render JSON-LD ở server, không phải ở client
Googlebot có chạy JavaScript, nhưng việc render bị xếp hàng và có thể trễ nhiều ngày. Dữ liệu có cấu trúc nên nằm sẵn trong HTML trả về:
// app/viec-lam/[slug]/page.tsx
export default async function JobPage({ params }: { params: { slug: string } }) {
const job = await getJob(params.slug);
const jsonLd = buildJobPosting(job);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* nội dung trang */}
</>
);
}
Kiểm tra nhanh bằng curl -s https://your-site/viec-lam/abc | grep -c 'application/ld+json' – nếu ra 0 thì schema đang được chèn ở client.
2. Thiếu addressLocality và postalCode
Đây là cảnh báo phổ biến nhất trong báo cáo "Tin tuyển dụng" của Search Console. Dữ liệu việc làm ở Việt Nam thường chỉ có tên tỉnh/thành, nên jobLocation.address hay bị thiếu trường. Cách mình xử lý là chuẩn hoá địa chỉ khi nhập tin và có bảng tra mã bưu chính theo tỉnh:
const POSTAL: Record<string, string> = {
"Hà Nội": "100000",
"Hồ Chí Minh": "700000",
"Đà Nẵng": "550000",
// ...
};
function buildAddress(job: Job) {
return {
"@type": "PostalAddress",
streetAddress: job.street ?? undefined,
addressLocality: job.ward ?? job.district ?? job.province,
addressRegion: job.province,
postalCode: POSTAL[job.province] ?? undefined,
addressCountry: "VN",
};
}
Lưu ý: chỉ điền giá trị có thật. Đừng điền chuỗi rỗng hay "N/A" – Google coi đó là giá trị không hợp lệ.
3. validThrough và tin hết hạn
Tin đã hết hạn mà vẫn để JobPosting sẽ bị Google đánh giá thấp chất lượng. Mình làm 3 việc khi tin hết hạn:
- Bỏ khối
JobPostingkhỏi trang. - Thêm
<meta name="robots" content="noindex">. - Vẫn trả
200và hiển thị gợi ý việc làm tương tự, để người dùng vào từ link cũ không bị cụt.
export async function generateMetadata({ params }) {
const job = await getJob(params.slug);
const expired = job.validThrough && new Date(job.validThrough) < new Date();
return { robots: expired ? { index: false, follow: true } : undefined };
}
4. hiringOrganization.sameAs trỏ sai
Một lỗi dễ bỏ qua: sameAs của công ty tuyển dụng lại trỏ về chính job board. sameAs phải là website hoặc trang chính thức của công ty đó. Nếu không có, hãy bỏ trường này thay vì điền URL của mình.
5. Sitemap và lastmod
Google chỉ tin lastmod khi nó phản ánh đúng lần nội dung thay đổi. Nếu job importer cập nhật updatedAt cho toàn bộ tin mỗi lần chạy, mọi URL sẽ có cùng lastmod và Google sẽ bỏ qua tín hiệu này. Chỉ cập nhật khi các trường hiển thị (tiêu đề, mô tả, lương, hạn nộp) thật sự đổi:
const changed = ["title", "description", "salary", "validThrough"]
.some((k) => existing[k] !== incoming[k]);
if (changed) incoming.updatedAt = new Date();
6. Cách kiểm tra
- Rich Results Test cho từng URL mẫu.
- Search Console → Cải thiện → Tin tuyển dụng, sau khi sửa bấm "Xác thực kết quả khắc phục".
- Theo dõi số trang hợp lệ trong vài tuần, vì Google cần thu thập lại.
Các ví dụ trên lấy từ quá trình vận hành Việc HOT, một trang tìm việc và tạo CV online miễn phí. Nếu bạn cũng đang làm job board, rất mong được trao đổi thêm trong phần bình luận.
All rights reserved