# Karriereseite Template in React: Bewerbung und JobPosting

> Ein Karriereseite Template in React, das Bewerbungen bringt: Mission, Team, Benefits, filterbare Stellen, Drawer, Bewerbungsformular und JobPosting für Google.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 6. Nov. 2026
- Kategorie: [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build)
- Lesezeit: 12 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/careers-page-template
- Englische Fassung: https://wingo-ui.com/blog/careers-page-template

## Kurz gesagt

Ein Karriereseite Template, das Bewerbungen bringt, braucht eine kurze Mission, echte Menschen, Benefits, die Schritte im Bewerbungsprozess, eine filterbare Liste der Stellen mit Gehaltsspannen und für jede Stelle eine eigene Seite mit einem Bewerbungsformular, das in deinem Postfach oder deinem ATS ankommt. Setz JobPosting Structured Data auf jede Stellenseite, nie auf die Liste, und liefere einen 404, sobald eine Stelle besetzt ist. Die Templates Careers Page und Job Detail Page von Wingo UI rendern die Oberfläche; die Stellen, die Texte und den Handler für die Bewerbungen lieferst du.

Eine Karriereseite hat genau eine Aufgabe: aus einem neugierigen Besucher eine Bewerbung zu machen, auf die du antworten kannst. Die meisten Karriereseite Templates hören bei der Optik auf. Sie liefern einen Hero, ein Raster mit Werten und eine Liste der Stellen und lassen genau die Teile weg, die entscheiden, ob sich überhaupt jemand bewirbt und ob Google deine Stellen listet. Dieser Guide zeigt ein Karriereseite Template in React und Next.js, das die ganze Arbeit erledigt: Mission, Team, Benefits, filterbare Stellen, ein Job-Drawer, ein Bewerbungsformular, eine Seite pro Stelle und JobPosting Structured Data. Die Oberfläche ist unser [Template Careers Page](https://wingo-ui.com/templates/careers-page), wir sind also nicht neutral. Datenmodell, Routen und Markup funktionieren aber mit jeder Vorlage für Stellenseiten.

## Was gehört in ein Karriereseite Template?

Ein Karriereseite Template sollte der Reihe nach die Fragen beantworten, die sich jemand vor der Bewerbung stellt: Warum gibt es die Firma, mit wem würde ich arbeiten, wie arbeitet man dort und was bekomme ich, wie läuft die Bewerbung ab, welche Stellen sind zu welchem Gehalt offen und was mache ich, wenn keine passt. Als wir am 6. November 2026 nach „careers page template“ gesucht haben, bestanden die Ergebnisse vor allem aus No-Code- und Page-Builder-Vorlagen (involve.me, WeWeb, Notion, die Careers-Liste von Webflow, ein Job-Board-Theme für Framer), einem Arbeitsblatt zum Ausdrucken und den Baukästen für Karriereseiten in Bewerbermanagementsystemen (ATS) wie Workable. Varianten wie „careers page template react“, „careers page design template“ oder „careers page example“ bringen mehr Seiten derselben Art. Wer eine Karriereseite gestalten will, findet dort gute Layouts. Fast keine davon sagt aber, wohin die Bewerbung geht oder wie eine Stelle in den Jobergebnissen von Google auftaucht.

Hier steht, wofür jeder Abschnitt da ist und welches Prop ihn in `CareersPage` steuert:

| Abschnitt | Die Frage, die er beantwortet | Im Template |
|---|---|---|
| Hero | Warum gibt es diese Firma, in einem Absatz? | `labels.heroTitle`, `labels.heroDescription` |
| Zahlen | Wie groß und wie alt ist sie? | `stats`, `showStats` |
| Team | Mit wem würde ich arbeiten? | `team`, `moments`, `showTeam` |
| Werte und Benefits | Wie arbeiten die Leute hier, und was bekomme ich? | `values`, `perks` |
| Bewerbungsprozess | Was passiert nach der Bewerbung, und wie lange dauert es? | `steps`, `showProcess` |
| Offene Stellen | Gibt es eine Stelle für mich, wo und für wie viel? | `roles`, `departments`, `groupByDepartment` |
| Initiativbewerbung | Was, wenn keine Stelle passt? | `showCta`, `labels.ctaPrimary` |

Zwei davon wiegen schwerer, als es aussieht. Gruppier die Stellen nach Team: Der [UX-Guide von Workable für Karriereseiten](https://resources.workable.com/tutorial/careers-page-ux) (aktualisiert im September 2023) empfiehlt das, und genau das macht `groupByDepartment`, solange der Chip All ausgewählt ist. Und zeig die Gehaltsspanne, denn in mehreren US-Bundesstaaten schreibt das Gesetz sie vor: Die [Liste der Bundesstaaten von Paycor](https://paycor.com/resource-center/articles/pay-transparency-laws-by-state) (aktualisiert am 20. November 2025) nennt Kalifornien, Colorado, Illinois, New York und Washington als Staaten, die Gehaltsangaben in Stellenanzeigen verlangen. In Colorado gilt das für jeden Arbeitgeber; die anderen vier setzen eine Mindestgröße voraus (4 Beschäftigte in New York, 15 in den übrigen). Prüf die Regeln für jeden Bundesstaat, in dem du einstellst.

## Wie installierst du das Karriereseite Template?

Ein Befehl kopiert beide Templates und alles, was sie importieren. Es sind Pro-Items, das Konto hinter der CLI braucht also Wingo UI Pro.

```bash
npx wingo-ui@latest add careers-page job-detail-page
```

Du bekommst `components/templates/careers-page.tsx` und `careers-page-data.ts`, `job-detail-page.tsx` und `job-detail-page-data.ts` sowie ihre Teile: den kostenlosen [Drawer](https://wingo-ui.com/components/drawer) für das Panel mit der Stelle, den kostenlosen [Search Input](https://wingo-ui.com/components/search-input) für den Stellenfilter, den kostenlosen [Empty State](https://wingo-ui.com/components/empty-state) für „No roles match those filters“, den [File Upload](https://wingo-ui.com/components/file-upload) für den Lebenslauf und die [Team Section](https://wingo-ui.com/components/team-section). Ohne Props rendert die Seite neun Beispielstellen einer ausgedachten Firma namens Acme.

> Live-Demo (Careers Page): Tipp „design“ ins Suchfeld, wähl einen Abteilungs-Chip und klick dann auf eine Stelle: Sie öffnet sich in einem Seitenpanel mit dem Bewerbungsformular. Auf dem Smartphone ist das Panel ein Bottom Sheet, das du zum Schließen nach unten ziehst. Probier sie unter [Careers Page](https://wingo-ui.com/templates/careers-page) aus und installier sie mit `npx wingo-ui@latest add careers-page`.

Die Liste kann drei Dinge, die eine statische Liste nicht kann. Suche, Abteilungs-Chips und ein Standort-Select grenzen die Stellen schon beim Tippen ein, und jeder Chip zeigt, wie viele Stellen er trifft. Ein Klick auf eine Stelle öffnet einen Drawer mit Beschreibung, Anforderungen, deinen Benefits und dem Formular, so kann jemand drei Stellen lesen, ohne den Filter zu verlieren. Und jede Zeile bleibt ein echter Link auf `/careers/<slug>`: Cmd- oder Strg-Klick öffnet die Seite, und Crawler folgen dem Link.

## Wie ersetzt du die Beispielinhalte?

Leg deine Stellen in einer typisierten Datei ab, aus der die Liste, die Stellenseiten und die strukturierten Daten lesen. Der Typ `JobDetail` der Stellenseite enthält schon alles, was die Karriereliste braucht, ein einziges Array speist also beide Templates. Ergänz die zwei Angaben, die strukturierte Daten brauchen und eine Zeile in der Liste nicht: wo Bewerber bei Remote-Stellen wohnen dürfen und für alle anderen Stellen die Büroadresse.

```ts
// app/careers/jobs.ts
import type { JobDetail } from "@/components/templates/job-detail-page-data"

type Office = { locality: string; region?: string; country: string }

export type Job = JobDetail & {
  postedAt: string
  // weglassen, wenn die Stelle offen bleibt, bis sie besetzt ist
  validThrough?: string
} & (
    | { workplace: "Remote"; eligibleCountries: string[] }
    | { workplace: "Hybrid" | "On-site"; office: Office }
  )

export const JOBS: Job[] = [
  {
    slug: "senior-frontend-engineer",
    title: "Senior Frontend Engineer",
    department: "Engineering",
    location: "Remote, USA",
    workplace: "Remote",
    eligibleCountries: ["US"],
    type: "Full-time",
    salaryMin: 160000,
    salaryMax: 190000,
    postedAt: "2026-11-02",
    isNew: true,
    summary: "Du verantwortest den Editor, den unsere Kunden jeden Morgen öffnen.",
    about: ["Du kommst in ein Team aus vier Engineers und einem Designer."],
    responsibilities: ["Den Editor mit React und TypeScript bauen"],
    requirements: ["5+ Jahre Erfahrung mit Web-Apps, davon 3+ mit React"],
  },
]
```

Dann renderst du die Liste aus einem kleinen Client-Wrapper, weil `onApply` eine Funktion ist und der Router-Link eine Client-Komponente:

```tsx
// app/careers/careers-view.tsx
"use client"

import Link from "next/link"
import { CareersPage } from "@/components/templates/careers-page"
import { JOBS } from "./jobs"
import { MOMENTS, PERKS, STEPS, TEAM, VALUES } from "./content"
import { sendApplication } from "./send-application"

export function CareersView() {
  return (
    <CareersPage
      roles={JOBS}
      departments={["Engineering", "Design", "Customer success"]}
      values={VALUES}
      perks={PERKS}
      team={TEAM}
      moments={MOMENTS}
      steps={STEPS}
      showStats={false}
      labels={{
        heroEyebrow: "Karriere bei Acme",
        heroDescription: "Wir sind sechs Leute und bauen ein Planungstool für kleine Teams, aus Brooklyn und drei US-Zeitzonen.",
        processTitle: "Drei Schritte, etwa zwei Wochen",
        ctaEmail: "jobs@acme.com",
      }}
      linkComponent={Link}
      // Header und Footer rendert schon das Layout der Site
      header={false}
      footer={false}
      onApply={sendApplication}
    />
  )
}
```

`content.ts` enthält deine Werte, Perks, das Team, die Momente und die Schritte, typisiert mit `CareersValue`, `CareersPerk`, `CareersTeamMember`, `CareersMoment` und `CareersStep` aus `careers-page-data.ts`. Geh auch die Standard-Labels durch. Der Beispieltext verspricht Dinge wie „We reply to every application within five working days“ und eine bezahlte Probeaufgabe. Das sind gute Versprechen, wenn du sie hältst, und ein schlechter erster Eindruck, wenn nicht. Die Beispielzahlen (42 Teammitglieder, 11 Länder) sind der Grund für `showStats={false}` im Beispiel: Schalte die Zahlen wieder ein, wenn du welche hast, auf die du stolz bist.

## Warum braucht jede Stelle eine eigene Seite?

Der Drawer ist eine Abkürzung für Menschen. Die Seite ist für alles andere da: für den Link, den eine Recruiterin in eine Nachricht kopiert, für die URL, die dein ATS per E-Mail verschickt, und als einziger Ort, an dem Google Job-Markup akzeptiert. Googles [Leitfaden zu strukturierten Daten für Stellenanzeigen](https://developers.google.com/search/docs/appearance/structured-data/job-posting) (geprüft am 6. November 2026) legt fest, dass JobPosting nur auf Seiten mit genau einer Stellenanzeige stehen darf und nicht auf Seiten, die Stellen auflisten. Die Karriereliste trägt also kein JobPosting, und jede Seite unter `/careers/<slug>` trägt genau eins.

```tsx
// app/careers/[slug]/page.tsx
import type { Metadata } from "next"
import { notFound } from "next/navigation"
import { JOBS } from "../jobs"
import { jobPostingJsonLd } from "../job-posting"
import { JobView } from "./job-view"

type Props = { params: Promise<{ slug: string }> }

// ein Slug, der nicht in JOBS steht, ist ein 404, so fällt eine entfernte Stelle aus Google heraus.
// mit aktiviertem cacheComponents entfällt diese Option in Next.js 16, dann erledigt das notFound() unten die Arbeit
export const dynamicParams = false

export function generateStaticParams() {
  return JOBS.map((job) => ({ slug: job.slug }))
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const job = JOBS.find((item) => item.slug === slug)
  if (!job) return {}
  return {
    title: `${job.title} (${job.location})`,
    description: job.summary,
    alternates: { canonical: `/careers/${job.slug}` },
  }
}

export default async function JobRoute({ params }: Props) {
  const { slug } = await params
  const job = JOBS.find((item) => item.slug === slug)
  if (!job) notFound()

  const similar = JOBS.filter((item) => item.department === job.department && item.slug !== job.slug)
  const jsonLd = jobPostingJsonLd(job, `https://acme.com/careers/${job.slug}`)

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }}
      />
      <JobView job={job} similar={similar} />
    </>
  )
}
```

```tsx
// app/careers/[slug]/job-view.tsx
"use client"

import Link from "next/link"
import { JobDetailPage } from "@/components/templates/job-detail-page"
import type { JobDetail, JobRole } from "@/components/templates/job-detail-page-data"
import { sendApplication } from "../send-application"

export function JobView({ job, similar }: { job: JobDetail; similar: JobRole[] }) {
  return (
    <JobDetailPage
      role={job}
      similarRoles={similar}
      linkComponent={Link}
      header={false}
      footer={false}
      onApply={sendApplication}
    />
  )
}
```

In Next.js 16 ist `params` ein Promise, deshalb warten beide Funktionen darauf. Das JSON-LD-Script steht in der Server-Seite und damit schon in der ersten HTML-Antwort, auch für Crawler, die nie JavaScript ausführen. Unser [Guide zu JSON-LD in Next.js](https://wingo-ui.com/de/blog/nextjs-json-ld) erklärt, warum `<` maskiert wird und wie du das Markup mit der Seite synchron hältst.

> Live-Demo (Job Detail Page): Scroll durch die Stelle: Auf dem Desktop bleibt die Bewerbungskarte neben der Beschreibung stehen. Unter 1024px erscheint unten eine Leiste mit Apply now; tipp darauf und sende das leere Formular ab, dann bekommt das erste fehlende Feld den Fokus. Probier sie unter [Job Detail Page](https://wingo-ui.com/templates/job-detail-page) aus und installier sie mit `npx wingo-ui@latest add job-detail-page`.

## Wie ergänzt du JobPosting Structured Data für die Jobsuche von Google?

Google verlangt fünf Properties: `title`, `description` (die volle Beschreibung als HTML), `datePosted`, `hiringOrganization` und `jobLocation`. Bei einer Stelle, die komplett remote ist, darf `applicantLocationRequirements` an die Stelle von `jobLocation` treten. Lohnend sind außerdem `validThrough`, `employmentType`, `baseSalary`, `identifier`, `directApply` und für Remote-Stellen `jobLocationType` zusammen mit `applicantLocationRequirements`. Bau alles aus demselben `Job`, den die Seite rendert, dann behauptet das Markup nie etwas, das der Leser nicht sieht:

```ts
// app/careers/job-posting.ts
import type { Job } from "./jobs"

const EMPLOYMENT = { "Full-time": "FULL_TIME", "Part-time": "PART_TIME", Contract: "CONTRACTOR" } as const

const escapeHtml = (text: string) =>
  text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
const list = (items: string[]) => `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`

export function jobPostingJsonLd(job: Job, url: string) {
  // derselbe Text, den die Seite zeigt, als html
  const description = [
    ...job.about.map((paragraph) => `<p>${escapeHtml(paragraph)}</p>`),
    `<h3>What you'll do</h3>${list(job.responsibilities)}`,
    `<h3>What you'll bring</h3>${list(job.requirements)}`,
  ].join("")

  return {
    "@context": "https://schema.org",
    "@type": "JobPosting",
    title: job.title,
    description,
    datePosted: job.postedAt,
    validThrough: job.validThrough,
    employmentType: EMPLOYMENT[job.type],
    url,
    directApply: true,
    identifier: { "@type": "PropertyValue", name: "Acme", value: job.slug },
    hiringOrganization: {
      "@type": "Organization",
      name: "Acme",
      sameAs: "https://acme.com",
      logo: "https://acme.com/logo.png",
    },
    ...(job.workplace === "Remote"
      ? {
          jobLocationType: "TELECOMMUTE",
          applicantLocationRequirements: job.eligibleCountries.map((name) => ({ "@type": "Country", name })),
        }
      : {
          jobLocation: {
            "@type": "Place",
            address: {
              "@type": "PostalAddress",
              addressLocality: job.office.locality,
              addressRegion: job.office.region,
              addressCountry: job.office.country,
            },
          },
        }),
    ...(job.salaryMin && job.salaryMax
      ? {
          baseSalary: {
            "@type": "MonetaryAmount",
            currency: job.salaryCurrency ?? "USD",
            value: { "@type": "QuantitativeValue", minValue: job.salaryMin, maxValue: job.salaryMax, unitText: "YEAR" },
          },
        }
      : {}),
  }
}
```

Ein paar Regeln aus demselben Leitfaden entscheiden, ob das funktioniert:

- `TELECOMMUTE` ist nur für Stellen gedacht, die zu 100% remote sind, und die Beschreibung muss das klar sagen. Dieser Satz gehört also in `about`. Eine hybride Stelle mit zwei Bürotagen behält ihre Büroadresse in `jobLocation`; deshalb macht der Typ `Job` das Feld `office` für alles Pflicht, was nicht Remote ist.
- Eine Remote-Stelle braucht über `applicantLocationRequirements` mindestens ein Land, in dem Bewerber wohnen dürfen.
- `baseSalary` ist die tatsächliche Spanne des Arbeitgebers, mit `unitText` als `HOUR`, `DAY`, `WEEK`, `MONTH` oder `YEAR` (Groß- und Kleinschreibung zählt).
- Ist eine Stelle besetzt, nimm sie offline: ein 404 oder 410, kein Markup mehr oder ein `validThrough` in der Vergangenheit. Wenn du sie aus `JOBS` löschst, hast du die erste Variante, denn die Route kennt nur die Slugs in `JOBS` und ruft für alle anderen `notFound()` auf. Google warnt, dass veraltete Stellen eine manuelle Maßnahme nach sich ziehen können.

Google nennt die Länder, in denen die Jobsuche (Google for Jobs) Ergebnisse zeigt; dazu gehören ganz Nordamerika und Lateinamerika sowie in Europa unter anderem Großbritannien, Deutschland und Frankreich. Teste eine live geschaltete Stellenseite mit dem [Test für Rich-Suchergebnisse](https://search.google.com/test/rich-results), bevor du dich darauf verlässt.

## Wohin sollen die Bewerbungen gehen?

Bewerbungen landen dort, wohin dein `onApply`-Handler sie schickt: bei einem Route Handler auf deinem eigenen Server, der sie speichert oder an dein ATS weiterleitet. Ohne `onApply` warten beide Templates 900 ms und zeigen dann die Bestätigung. In einer Demo ist das richtig, auf einer Live-Site ist es ein stiller Fehler, also schließ den Handler vor dem Launch an. Der Helper unten funktioniert für beide Templates, weil ihre Bewerbungsobjekte diese Felder gemeinsam haben. Die Stellenseite schickt zusätzlich `consent` mit, die Antwort auf ihre Checkbox, ob Acme die Bewerbung 12 Monate aufbewahren darf. Speicher sie zusammen mit der Bewerbung:

```ts
// app/careers/send-application.ts
type Application = {
  role?: { slug: string }
  name: string
  email: string
  linkedin: string
  note: string
  resume?: File
  // schickt nur die Stellenseite
  consent?: boolean
}

// bei einem Reject zeigt das Formular seine "failed"-Meldung
export async function sendApplication(application: Application) {
  const body = new FormData()
  body.set("role", application.role?.slug ?? "open")
  body.set("name", application.name)
  body.set("email", application.email)
  body.set("linkedin", application.linkedin)
  body.set("note", application.note)
  if (application.consent !== undefined) body.set("consent", String(application.consent))
  if (application.resume) body.set("resume", application.resume)
  const response = await fetch("/api/applications", { method: "POST", body })
  if (!response.ok) throw new Error(`Bewerbung fehlgeschlagen: ${response.status}`)
}
```

Schick die Bewerbung an einen Route Handler, nicht an eine Server Action. Das Feld für den Lebenslauf nimmt PDF- und Word-Dateien bis 10 MB an, und laut der Doku von Next.js 16 sind Anfragen an Server Actions auf 1 MB begrenzt, solange du `serverActions.bodySizeLimit` nicht hochsetzt. Prüf auch, wie groß Anfragen bei deinem Hoster sein dürfen.

```ts
// app/api/applications/route.ts
const MAX_RESUME = 10 * 1024 * 1024
const RESUME_TYPES = [
  "application/pdf",
  "application/msword",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
]

export async function POST(request: Request) {
  const form = await request.formData()
  const name = String(form.get("name") ?? "").trim()
  const email = String(form.get("email") ?? "").trim()
  const resume = form.get("resume")

  if (!name || !/^\S+@\S+\.\S+$/.test(email)) {
    return Response.json({ error: "Name und eine gültige E-Mail-Adresse sind Pflicht" }, { status: 400 })
  }
  if (resume instanceof File && (resume.size > MAX_RESUME || !RESUME_TYPES.includes(resume.type))) {
    return Response.json({ error: "Der Lebenslauf muss eine PDF- oder Word-Datei bis 10 MB sein" }, { status: 400 })
  }

  // speichern oder an dein ATS weiterleiten, mit dem geheimen Schlüssel, der nie im Browser landet
  return Response.json({ ok: true }, { status: 201 })
}
```

Wenn du schon ein ATS nutzt, lass es die Quelle der Wahrheit bleiben. Greenhouse ist ein gutes Beispiel: Die [Job Board API](https://docs.greenhouse.io/job-board.html) (geprüft am 6. November 2026) liefert deine Stellen öffentlich und ohne Authentifizierung aus, und `content=true` ergänzt die volle Beschreibung, die Abteilung und das Büro. `JOBS` kann also ein Fetch zur Build-Zeit sein. Der Endpunkt für Bewerbungen braucht deinen Job Board API Key per Basic Auth, und die Doku rät, Formular-Posts über deinen eigenen Server zu leiten. Genau das meint der Kommentar im Handler oben. Der [Guide zum Kontaktformular in Next.js](https://wingo-ui.com/de/blog/nextjs-contact-form) behandelt Spamschutz und E-Mail-Versand für dieselbe Art von Handler.

## Wie verhält sich die Karriereseite auf dem Smartphone?

Auf dem Smartphone ändern die Templates ihre Form, statt nur zu schrumpfen. Unter 640px scrollen die Abteilungs-Chips in einer Reihe seitwärts. Unter 768px öffnet das Standort-Select ein Bottom Sheet. Der Job-Drawer wird zum Bottom Sheet, das du zum Schließen nach unten ziehst, und im Feld für den Lebenslauf reicht ein Tipp, um eine Datei zu wählen. Auf der Stellenseite wird die mitlaufende Bewerbungskarte unter 1024px zu einer Leiste am unteren Rand, oberhalb der Safe Area, und zwar nur, solange das Formular nicht zu sehen ist.

Auch die Tastatur funktioniert: `/` fokussiert das Suchfeld, Enter auf einer Stelle öffnet den Drawer, Escape schließt ihn, und auf der Stellenseite springt der Fokus nach einem fehlgeschlagenen Absenden auf das erste Feld mit einem Problem. Die Zeilen mit den Stellen sind echte Links, Screenreader kündigen sie also als Links an, und ein Mittelklick öffnet einen neuen Tab.

## Gehört das Team auf die Karriereseite oder auf die About-Seite?

Auf beide, mit unterschiedlichen Aufgaben. Auf der Karriereseite beantwortet das Team die Frage „Mit wem würde ich arbeiten?“, und das Design `bold` ersetzt das Foto-Mosaik durch die [Team Section](https://wingo-ui.com/components/team-section) mit ihrer Kachel „We're hiring“. Auf deiner About-Seite verlinkt derselbe Block zurück auf die Karriereseite und zählt die offenen Stellen:

```tsx
// app/about/team.tsx
import { TeamSection } from "@/components/blocks/team-section"
import { JOBS } from "@/app/careers/jobs"

export function AboutTeam() {
  return (
    <TeamSection
      title="Die Leute hinter Acme"
      description="Sechs Leute in Brooklyn und verteilt über drei US-Zeitzonen."
      members={[{ name: "Emma Carter", role: "Gründerin" }]}
      layout="cards"
      columns={3}
      openRoles={JOBS.length}
      hiring={JOBS.length > 0}
      hiringHref="/careers"
    />
  )
}
```

> Live-Demo (Team Section): Das Layout cards mit drei offenen Stellen: Die letzte Kachel verlinkt auf die Karriereseite und zählt sie. Auf dem Smartphone stapeln sich die Karten in einer Spalte. Probier sie unter [Team Section](https://wingo-ui.com/components/team-section) aus und installier sie mit `npx wingo-ui@latest add team-section`.

Setz `hiring={false}`, wenn keine Stelle offen ist, damit die Kachel niemanden zu einer leeren Liste einlädt. Unser [Guide zum SaaS Website Template](https://wingo-ui.com/de/blog/saas-website-template) zeigt, wie sich die Karriereseite in die übrigen Seiten einer Firmen-Website einfügt, und in der Kategorie [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build) findest du weitere Screens, die genauso gebaut sind.

## Welche Fehler passieren auf Karriereseiten am häufigsten?

- **Beispieltext stehen gelassen.** Die 40.000 Teams von Acme und das Versprechen, innerhalb von fünf Tagen zu antworten, gehören nicht dir. Durchsuch die Labels vor dem Launch nach „Acme“.
- **Keine Gehaltsspannen.** Mehrere US-Bundesstaaten verlangen sie in Stellenanzeigen, und eine Spanne in der Liste erspart beiden Seiten eine Gesprächsrunde, wenn das Gehalt nicht passt.
- **Abgelaufene Stellen noch online.** Ein Array, ein 404, sobald eine Stelle daraus verschwindet.
- **JobPosting auf der Listenseite.** Google akzeptiert es dort nicht; ein Posting pro Stellenseite.
- **Ein Bewerbungsformular, das ins Leere führt.** Teste `onApply` mit einer echten Datei und lass die Funktion bei einem Fehler rejecten, damit das Formular den Fehler auch anzeigt.

## Nächster Schritt

Klick dich durch die [Careers Page](https://wingo-ui.com/templates/careers-page) und die [Job Detail Page](https://wingo-ui.com/templates/job-detail-page) in allen ihren Designs, installier sie dann mit `npx wingo-ui@latest add careers-page job-detail-page` und ersetz die Beispielstellen durch deine eigenen. Beide Templates und die Team Section gehören zu Wingo UI Pro: $8 im Monat, $80 im Jahr oder einmalig $150 ([Preise](https://wingo-ui.com/de/pricing)). Wenn du lieber selbst eine Karriereseite erstellen willst, enthält ein kostenloses Konto die freien Teile wie Drawer und Search Input.

## Komponenten in diesem Artikel

- [Careers Page](https://wingo-ui.com/templates/careers-page) (Pro): A React careers page template: mission, team mosaic, values, benefits, filterable open roles with a job drawer and apply form. Installation: `npx wingo-ui@latest add careers-page`
- [Job Detail Page](https://wingo-ui.com/templates/job-detail-page) (Pro): A React job posting page template: the role, benefits, interview steps, team, a sticky apply card, an application form and similar roles. Installation: `npx wingo-ui@latest add job-detail-page`
- [Team Section](https://wingo-ui.com/components/team-section) (Pro): A React team section for an about page: portrait cards, tall portrait cards, a compact roster or a list with bios, plus a "We're hiring" tile. Installation: `npx wingo-ui@latest add team-section`

## Häufige Fragen

### Was gehört auf eine Karriereseite?

Eine Mission in einem Absatz, das Team, die Werte und Benefits, der Ablauf der Bewerbung und die offenen Stellen, nach Abteilung gruppiert, mit Ort, Art der Anstellung und Gehaltsspanne. Jede Stelle braucht eine eigene Seite mit der vollen Beschreibung und einem Bewerbungsformular, dazu kommt eine Initiativbewerbung für alle, die keine passende Stelle finden.

### Kann ich JobPosting Structured Data auf die Seite mit der Stellenliste setzen?

Nein. Laut Googles Doku zu Stellenanzeigen darf das Markup nur auf Seiten stehen, die genau eine Stellenanzeige enthalten, und nicht auf Seiten, die Stellen auflisten. Setz es auf jede einzelne Stellenseite, etwa /careers/senior-frontend-engineer.

### Wie kennzeichne ich eine Remote-Stelle für Google for Jobs?

Setz jobLocationType auf TELECOMMUTE und führ unter applicantLocationRequirements die Länder auf, in denen Bewerber wohnen dürfen. Google akzeptiert TELECOMMUTE nur für Stellen, die komplett remote sind, eine hybride Stelle behält also ihre Büroadresse in jobLocation.

### Was passiert mit einer Stellenanzeige, wenn die Stelle besetzt ist?

Nimm sie offline: Liefere für die Seite einen 404 oder 410, entferne das Markup oder setz validThrough auf ein vergangenes Datum. Google warnt, dass abgelaufene Stellen, die online bleiben, zu einer manuellen Maßnahme führen können.

### Ist das Karriereseite Template von Wingo UI kostenlos?

Nein. Careers Page, Job Detail Page, Team Section und der File Upload, den sie für Lebensläufe nutzen, sind Pro-Items. Andere Teile, auf denen sie aufbauen, wie Drawer, Search Input, Empty State und Marketing Section, sind kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/careers-page-template
