capturia ~ /careers/senior-dev
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
export async function POST(req: NextRequest) {
const { leadId, stage } = await req.json();
const { data } = await supabase
.from('client_contacts')
.update({ pipeline_stage: stage })
.eq('id', leadId).select().single();
await triggerWorkflow('stage_changed', data);
return NextResponse.json({ success: true });
}
 
const useLeadPipeline = (leadId: string) => {
return useQuery({
queryKey: ['pipeline', leadId],
queryFn: () => fetchPipelineData(leadId),
staleTime: 60_000,
});
};
 
export function StageCard({ stage }: Props) {
const { mutate } = useMutation({
mutationFn: updateStage,
onSuccess: () => invalidate(['pipeline']),
});
return <Card onClick={() => mutate(stage)} />;
}
$ON RECRUTE

Senior Dev Full-Stack recherchéViens bâtir la suite.

Découvrir le poste
system-status
$
Edge Functions en prod
Intégrations actives
Framework
TypeScript mode
Uptime production
Remote
$
Next.jsReactTypeScriptSupabaseVercelTailwind CSSn8nAnthropicOpenAIGitHub

Leproduitestsolide,lesclientssontlà,l'équipetechniquegrandit.Onchercheunseniordevquivaprendredel'ownershipsurl'architectureetnousaideràpasserauprochainniveau.

D
Dany Therrien
Fondateur, Capturia
🏗️

Un vrai produit, pas un prototype

65+ edge functions en production, des intégrations OAuth, des workflows automatisés, des clients qui utilisent le système chaque jour. Tu arrives pas dans un greenfield — tu arrives dans quelque chose qui roule.

🔑

Tes décisions comptent pour vrai

Tu proposes une architecture, on en discute, et ça ship. Personne va prendre la décision à ta place. C'est pas juste un buzzword dans l'offre d'emploi — l'ownership est réel.

📲

Le fondateur est à un message

Pas de sprint planning à 12 personnes, pas de PM entre toi et les décisions. Une question? Message direct. Une réponse? 24h max. C'est aussi simple que ça.

La stack en production

Tout ce qui suit roule en prod aujourd'hui — c'est pas une wish list.

Frontend
Next.js 16React 19TypeScript strictTailwind CSS v4Framer MotionRadix UI
Backend
SupabaseEdge FunctionsPostgreSQL + RLSTanStack QueryStripe ConnectReact Email + Resend
IA & Automation
Claude CodeOpenAI APIAnthropic SDKn8n / ZapierTwilioAssemblyAI
Infra
VercelSupabase CLIGitHub (main only)MCP Tools

$ Si tu connais déjà ces outils, tu vas être productif dès la première semaine.

Comment on sait que ça marche

Pas de performance review annuelle avec des métriques inventées. Cinq choses concrètes qu'on regarde.

kpis.test.ts
$

À quoi ressemble une journée

Pas de réunions à rallonge, pas de standup inutile. Du vrai travail de dev.

$ git log --oneline --graph
*
9h00

Check système

Tu regardes Vercel, les logs Supabase, les dashboards. T'as pas besoin que quelqu'un te dise si quelque chose a planté — tu le vois toi-même.

*
9h30

Architecture avant code

Tu lis le code existant, tu identifies les impacts de ce que tu vas faire. Après ça seulement, tu commences à coder.

*
11h30

DB + Backend

Migrations MCP, politiques RLS, structure de données. Tu prends ces décisions-là sans demander la permission.

*
14h00

Code review

Tu reviews le code de l'équipe et l'équipe review le tien. Pas juste ce qui est wrong — pourquoi c'est wrong et comment faire mieux.

*
15h30

Validate + deploy

npm run validate. Zéro warning. Tu pushes en production en sachant exactement ce que tu livres.

*
16h30

Sync avec le fondateur

C'est pas une réunion formelle. Trois messages pour s'aligner sur la prochaine priorité et c'est réglé.

Es-tu la bonne personne?

On va être transparent — voici exactement ce qu'on cherche.

Ce qu'on cherche absolument

  • 5+ ans en développement full-stack avec React/Next.js et un backend solide
  • T'as déjà pris ownership d'une codebase existante — pas juste fermé des tickets
  • TypeScript strict sans any — des interfaces, des génériques, des types explicites
  • Tu prends des décisions d'architecture et tu sais les défendre quand on te challenge
  • PostgreSQL pour vrai : migrations, contraintes, row-level security

Un gros plus

  • Expérience avec Supabase (Auth, RLS, Edge Functions, Realtime)
  • Intégrations OAuth complexes (Zoom, Google, Stripe Connect)
  • SaaS multi-tenant avec de vrais clients qui paient
  • Expérience à guider ou reviewer d'autres développeurs

Red flags

Tu livres du code sans avoir lu ce qui existe déjà
Tu prends des décisions sans évaluer l'impact en production
T'attends qu'on te dise quoi faire au lieu de prendre des initiatives
Aucun pattern consistant — chaque problème, tu repars de zéro

CANDIDATURE

esc
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
~`
!1
@2
#3
$4
%5
^6
&7
*8
(9
)0
_
+=
delete
tab
Q
W
E
R
T
Y
U
I
O
P
{[
}]
|\
caps lock
A
S
D
F
G
H
J
K
L
:;
"'
return
shift
Z
X
C
V
B
N
M
<,
>.
?/
shift
fn
control
option
command
command
option
CANDIDATURE

Ça t'parle?

Salaire selon ton expérience et tes compétences. 100% remote, basé au Québec.

application.tsx
Montre-nous qui tu es

Ton parcours, un projet dont t'es fier, et comment tu réfléchis face à des vrais défis.

01Curriculum vitæ

Ton CV à jour — on veut voir ton parcours, tes projets, et ce que t'as bâti.

02Portfolio(optionnel)

Un projet dont t'as eu la responsabilité complète. Pas juste contribué — owned.

Tes informations sont confidentielles et ne seront jamais partagées.