Skip to content

feat(seo): add ai resume prompts hub#526

Merged
aafre merged 3 commits into
mainfrom
seo/ai-prompts-hub-build
May 25, 2026
Merged

feat(seo): add ai resume prompts hub#526
aafre merged 3 commits into
mainfrom
seo/ai-prompts-hub-build

Conversation

@aafre
Copy link
Copy Markdown
Owner

@aafre aafre commented May 13, 2026

Summary

  • Adds the live /blog/ai-resume-prompts-hub page with comparison table, reviewed-date wording, FAQPage JSON-LD, internal links, and outbound provider references.
  • Registers the hub in App routing, blog metadata, and manual sitemap data with 2026-05-13 freshness metadata.
  • Keeps the four old AI prompt pages live with no redirects, no redirectTo fields, and no sitemap removals for PR A.

Test Plan

  • npx tsc --noEmit -p tsconfig.json
  • npx eslint src/components/blog/AIResumePromptsHub.tsx
  • npx vitest run --reporter=dot
  • npm run build

Notes

@aafre aafre self-assigned this May 13, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the 'AI Resume Prompts Hub' blog post, adding a new component, routing, and metadata for comparing AI tools in resume writing. Feedback suggests aligning with repository standards by passing FAQ data directly to the BlogLayout component for centralized rendering and schema generation. Additionally, it is recommended to import shared constants from a single source of truth to avoid duplication and to use prefixed indices for React keys in dynamic lists to ensure uniqueness.

Comment on lines +242 to +265
const faqSchema = generateFAQPageSchema(HUB_FAQS);
const combinedSchema = wrapInGraph([faqSchema]);

return (
<>
<Helmet>
<script type="application/ld+json">{JSON.stringify(combinedSchema)}</script>
</Helmet>
<BlogLayout
title="AI Resume Prompts Hub: Best Prompts for ChatGPT, Claude, Gemini & More"
description="Compare Claude, ChatGPT, Gemini, Grok, Copilot, and DeepSeek for resume writing. Pick the best AI prompt for bullets, summaries, ATS keywords, and cover letters."
publishDate={REVIEW_DATE}
lastUpdated={REVIEW_DATE}
readTime="12 min"
keywords={[
"ai resume prompts",
"best ai for resume writing",
"chatgpt resume prompts",
"claude resume prompts",
"gemini resume prompts",
"ats keyword prompts",
]}
ctaType="resume"
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository's general rules, blog posts with an FAQ section should pass the questions and answers as a prop to the component. The layout component is responsible for both rendering the FAQs and generating the FAQPage JSON-LD schema. This ensures consistency and reduces boilerplate in individual blog components.

Suggested change
const faqSchema = generateFAQPageSchema(HUB_FAQS);
const combinedSchema = wrapInGraph([faqSchema]);
return (
<>
<Helmet>
<script type="application/ld+json">{JSON.stringify(combinedSchema)}</script>
</Helmet>
<BlogLayout
title="AI Resume Prompts Hub: Best Prompts for ChatGPT, Claude, Gemini & More"
description="Compare Claude, ChatGPT, Gemini, Grok, Copilot, and DeepSeek for resume writing. Pick the best AI prompt for bullets, summaries, ATS keywords, and cover letters."
publishDate={REVIEW_DATE}
lastUpdated={REVIEW_DATE}
readTime="12 min"
keywords={[
"ai resume prompts",
"best ai for resume writing",
"chatgpt resume prompts",
"claude resume prompts",
"gemini resume prompts",
"ats keyword prompts",
]}
ctaType="resume"
>
return (
<>
<BlogLayout
title="AI Resume Prompts Hub: Best Prompts for ChatGPT, Claude, Gemini & More"
description="Compare Claude, ChatGPT, Gemini, Grok, Copilot, and DeepSeek for resume writing. Pick the best AI prompt for bullets, summaries, ATS keywords, and cover letters."
publishDate={REVIEW_DATE}
lastUpdated={REVIEW_DATE}
readTime="12 min"
keywords={[
"ai resume prompts",
"best ai for resume writing",
"chatgpt resume prompts",
"claude resume prompts",
"gemini resume prompts",
"ats keyword prompts",
]}
ctaType="resume"
faqs={HUB_FAQS}
>
References
  1. For blog posts with an FAQ section, extract the questions and answers into a const FAQS array. Pass this array as a prop to the component, which will handle both rendering the FAQs and generating the FAQPage JSON-LD schema.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — verified BlogLayout.tsx:33 declares faqs?: BlogFAQ[] and lines 98–104 auto-generate FAQPage schema. Eight existing blog files (ResumeLengthGuide, ResumeKeywordsGuide, ResumeActionVerbs, etc.) use this pattern. Fixing in a follow-up commit: switching to faqs={HUB_FAQS} and dropping the manual Helmet/wrapInGraph block.

Comment on lines +417 to +427
<section>
<h2 className="text-2xl font-bold text-ink">AI Resume Prompts FAQ</h2>
<div className="mt-4 space-y-4">
{HUB_FAQS.map((faq) => (
<div key={faq.question} className="rounded-lg border border-black/[0.06] bg-white p-4">
<h3 className="text-lg font-bold text-ink">{faq.question}</h3>
<p className="mt-2 text-stone-warm">{faq.answer}</p>
</div>
))}
</div>
</section>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This manual FAQ rendering section should be removed, as the component is intended to handle FAQ rendering via the faqs prop, per the general rules.

References
  1. For blog posts with an FAQ section, extract the questions and answers into a const FAQS array. Pass this array as a prop to the component, which will handle both rendering the FAQs and generating the FAQPage JSON-LD schema.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not actioning. BlogLayout's faqs prop only emits the JSON-LD schema (BlogLayout.tsx:98–104) — it does NOT render visible FAQ JSX. Verified against ResumeLengthGuide.tsx, which uses faqs={FAQS} AND renders the visible FAQ section in the body at line 587 ("Frequently Asked Questions"). Per Google's structured-data guidelines, FAQPage schema must match visible page content. Keeping the JSX intact.

Comment on lines +221 to +230
const INTERNAL_LINKS = [
{ href: "/templates", label: "Browse resume templates" },
{ href: "/free-resume-builder-no-sign-up", label: "Build a resume without sign-up" },
{ href: "/blog/claude-resume-prompts", label: "Claude resume prompts" },
{ href: "/blog/gemini-resume-prompts", label: "Gemini resume prompts" },
{ href: "/blog/best-free-resume-builders-2026", label: "Best free resume builders" },
{ href: "/blog/ai-cover-letter-prompts", label: "AI cover letter prompts" },
{ href: "/blog/ai-resume-writing-guide", label: "AI resume writing guide" },
{ href: "/resume-keyword-scanner", label: "ATS keyword scanner" },
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid data duplication, shared constants like blog post slugs and titles should be imported from a single source of truth (e.g., src/data/blogPosts.ts). The INTERNAL_LINKS array currently hardcodes several paths that are already defined in the blog metadata.

References
  1. To avoid data duplication, import shared constants from a single source of truth and map over them to add component-specific metadata if needed.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considered. Net duplication is ~5 slug strings out of 8 entries (3 — /templates, /free-resume-builder-no-sign-up, /resume-keyword-scanner — aren't blog posts and can't be imported from blogPosts.ts anyway). Labels are hub-specific UX copy that wouldn't come from imports either. Mixing import-derived and hardcoded entries in one array adds complexity for marginal benefit. Skipping for now; can revisit if slug drift becomes a real problem.

Comment on lines +371 to +373
{model.prompts.map((prompt) => (
<li key={prompt}>{prompt}</li>
))}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When rendering lists in React where items lack stable IDs, use a prefixed index to create unique keys. This prevents potential collisions with other lists on the page and follows the repository's defensive patterns for list rendering.

Suggested change
{model.prompts.map((prompt) => (
<li key={prompt}>{prompt}</li>
))}
{model.prompts.map((prompt, index) => (
<li key={'prompt-' + index}>{prompt}</li>
))}
References
  1. When rendering dynamic lists in React where items lack stable IDs, use a prefixed index (e.g., key={'prefix-' + index}) to create unique keys and prevent collisions with other lists.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not actioning. The "prefixed index" rule applies when items lack stable IDs. Here, each prompt string is itself unique (all 18 prompts across 6 AIs are distinct) and acts as the stable ID. React's docs explicitly recommend content-derived keys when uniqueness is guaranteed. Current key={prompt} is correct.

aafre added a commit that referenced this pull request May 25, 2026
release: post-cliff bundle — integration of #523 + #526 + #529 for dev verification
@aafre aafre merged commit 7b31b82 into main May 25, 2026
6 checks passed
@aafre
Copy link
Copy Markdown
Owner Author

aafre commented May 25, 2026

Shipped via #532 (post-cliff bundle → main). Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant