Careers: open a PR adding a server-rendered /careers page - #188
Merged
Merged
Conversation
The careers widget paints its board client-side, which is fine for people and useless for crawlers — precisely the failure CrawlProof audits for. This adds the crawlable counterpart: a button on the careers dashboard that opens a pull request putting a real /careers route in the customer's own repo, so the roles ship as HTML on their domain with the JobPosting graph in the source. Deterministic, like install-tracker and install-ad. We write a file only where the framework is positively identified — Next.js App Router or Astro — and never overwrite a careers page that already exists. Every other repo gets an honest no-op saying the widget already covers them; writing a speculative page into someone's repo is worse than opening no PR at all. The generated file can't import from us, so the feed now returns the finished JobPosting graph alongside the roles. That keeps the schema.org rules in one tested place instead of a copy that drifts. The widget learns about server-rendered boards: a container marked data-cp-careers-ssr gets replaced rather than appended to, and its JSON-LD is left alone, so a visitor never sees each job twice and a crawler never reads a duplicate graph. Verified: tsc clean, 1357 tests pass, next build compiles. The generated page was emitted into this repo's own tsconfig to prove it compiles in a strict Next + TypeScript project. Escaping, the JSON-LD breakout guard, the overwrite protection, and the unknown-framework no-op were each broken on purpose to confirm a test catches them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThreatCrush Security Scan54 finding(s) HIGH/CRITICAL: 10 | MEDIUM: 44
…and 4 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds PR support to the careers dashboard — the crawlable counterpart to the drop-in widget.
Why
The widget paints the job board client-side. That's fine for people and useless for crawlers, which is exactly the failure CrawlProof audits for elsewhere. Google for Jobs will not index a board that exists only as JavaScript.
This puts a real
/careersroute in the customer's own repo, so the roles ship as HTML on their domain with theJobPostinggraph in the source rather than injected after load. The widget then upgrades that same markup into the inline application form — crawlers get HTML, humans get the form, and the two say the same thing.What it does
On Stats → Careers, a "Add to my repo" panel: pick a repo, Check repo (read-only detection, nothing is written), then Open pull request.
Deterministic, like
install-trackerandinstall-ad— no LLM, no guessing:app/orsrc/app/,.tsxor.jsx) or Astro. Monorepo subdirectories supported.Notable decisions
The feed now returns the finished
json_ld. The generated file can't import our helpers, so the alternative was a second copy of the schema.org rules drifting from the tested one. Same reasoning the feed already uses foremployment_type_schemaandcanonical_url.The widget learns about SSR boards. A container marked
data-cp-careers-ssrgets replaced rather than appended to, and its JSON-LD is left alone — otherwise the visitor sees every job twice and the crawler reads a duplicate graph. The clear only ever fires on that attribute, never on the<main>/<body>fallback.Astro's caching is described honestly. A statically built Astro site bakes the roles in at build time, so the PR body and the generated file both say roles appear on the next deploy, rather than repeating Next's "cached for 5 minutes".
Verification
tsc --noEmitclean;next buildcompiles; 1357 tests pass (31 new)."in an href, and a</script>payload inside the JSON-LD.<guard, the overwrite protection, and the unknown-framework no-op each fails a test.Migration
20260804120000_pr_runs_install_careers.sqlwidens theproject_pr_runs.kindcheck to allowinstall_careers. Not yet applied — say the word.Not covered
SvelteKit, Nuxt, and the Next Pages Router fall through to the no-op. Their templates are different enough that a shared one would be a guess; the widget still covers those sites client-side.
🤖 Generated with Claude Code