Overview
Vitaes allows you to share your resume publicly via a unique URL. When you make a resume public, anyone with the link can view and download it, and you can track engagement through view and download analytics.
Making a Resume Public
You can toggle your resume’s public status from the dashboard:
Navigate to your dashboard
Find the resume card you want to share
Click the three-dot menu (⋮)
Select Make Public
Each resume has a unique slug that’s generated from your email and resume name. This slug becomes part of the public URL.
Programmatic Public Status Update
export const appRouter = {
updateResumePublicStatus: protectedProcedure
. input ( z . object ({ id: z . string (), isPublic: z . boolean () }))
. handler ( async ({ context , input }) => {
const { id : resumeId , isPublic } = input
const currentUser = context . session . user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
await db
. update ( resume )
. set ({ isPublic , updatedAt: new Date () })
. where ( eq ( resume . id , resumeId ))
return updatedResume
}),
}
Public Resume URLs
Once a resume is public, it’s accessible at:
https://yourdomain.com/view/{slug}
The slug is generated using:
slug : uniqueSlug ( currentUser . email , name )
Share Dialog
When you click “Share” on a public resume, a dialog appears with the shareable URL:
export function ShareDialog ({
open ,
slug ,
onOpenChange ,
} : ShareDialogProps ) {
const [ copied , setCopied ] = useState ( false )
const shareUrl = ` ${ globalThis . location . origin } /view/ ${ slug } `
const handleCopy = async () => {
try {
await navigator . clipboard . writeText ( shareUrl )
setCopied ( true )
toast . success ( 'Link copied to clipboard!' )
setTimeout (() => {
setCopied ( false )
}, 2000 )
} catch {
// Fallback for older browsers
const textArea = document . createElement ( 'textarea' )
textArea . value = shareUrl
textArea . style . position = 'fixed'
textArea . style . opacity = '0'
document . body . appendChild ( textArea )
textArea . select ()
await navigator . clipboard . writeText ( shareUrl )
textArea . remove ()
}
}
return (
< Dialog open = { open } onOpenChange = { onOpenChange } >
< DialogContent >
< DialogHeader >
< DialogTitle > Share Resume </ DialogTitle >
< DialogDescription >
Anyone with this link can view your resume
</ DialogDescription >
</ DialogHeader >
< div className = "py-4" >
< div className = "flex gap-2" >
< Input
readOnly
value = { shareUrl }
className = "flex-1 font-mono text-sm"
onClick = {(e) => e.currentTarget.select()}
/>
< Button
type = "button"
variant = "outline"
onClick = { handleCopy }
>
{ copied ? < Check /> : < Copy />}
</ Button >
</ div >
</ div >
</ DialogContent >
</ Dialog >
)
}
The share dialog includes a one-click copy button that automatically copies the URL to your clipboard with visual feedback.
View Tracking
Public resumes automatically track page views:
getResumeBySlug : publicProcedure
. input ( z . object ({ slug: z . string () }))
. handler ( async ({ context , input }) => {
const { slug : resumeSlug } = input
const queriedResume = await db . query . resume . findFirst ({
where : ({ slug }, { eq }) => eq ( slug , resumeSlug ),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
// Check permissions
if (
! queriedResume . isPublic &&
queriedResume . userEmail !== context . session ?. user . email
) {
throw new ORPCError ( 'FORBIDDEN' )
}
// Only increment views for public resumes
if ( queriedResume . isPublic ) {
await db
. update ( resume )
. set ({ views: queriedResume . views + 1 })
. where ( eq ( resume . id , queriedResume . id ))
}
return queriedResume
})
Views are only tracked for public resumes. Private resumes accessed by their owner don’t increment the view counter.
Download Tracking
Public resumes also track downloads:
setDownloadCount : publicProcedure
. input ( z . object ({ id: z . string () }))
. handler ( async ({ context , input }) => {
const { id : resumeId } = input
const queriedResume = await db . query . resume . findFirst ({
where : ({ id }, { eq }) => eq ( id , resumeId ),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
// Check permissions
if (
! queriedResume . isPublic &&
queriedResume . userEmail !== context . session ?. user . email
) {
throw new ORPCError ( 'FORBIDDEN' )
}
// Only increment downloads for public resumes
if ( queriedResume . isPublic ) {
await db
. update ( resume )
. set ({ downloads: queriedResume . downloads + 1 })
. where ( eq ( resume . id , resumeId ))
}
return queriedResume
})
Analytics on Resume Cards
Public and private resumes display different information on their cards:
< CardFooter className = "flex items-center justify-between border-t p-4" >
< div className = "flex items-center gap-4 text-xs text-muted-foreground" >
{ resume . isPublic ? (
<>
< div className = "flex items-center gap-1" >
< Globe className = "size-3" />
< span >{resume. views } views </ span >
</ div >
< div className = "flex items-center gap-1" >
< Download className = "size-3" />
< span >{resume. downloads } </ span >
</ div >
</>
) : (
< div className = "flex items-center gap-1" >
< Lock className = "size-3" />
< span > Private </ span >
</ div >
)}
</ div >
</ CardFooter >
Public Resume Card
Private Resume Card
Shows:
Globe icon with view count
Download icon with download count
Share button in the menu
Shows:
Lock icon with “Private” label
No view or download counts
“Make Public” option in menu
Making a Resume Private
To make a public resume private again:
Open the resume card menu (⋮)
Select Make Private
The resume will no longer be accessible via its public URL
View and download counts are preserved but stop incrementing
Making a resume private doesn’t delete the slug or reset analytics. If you make it public again later, the same URL will work and analytics will continue from where they left off.
Security and Privacy
Access Control
Public resumes : Accessible to anyone with the link
Private resumes : Only accessible by the owner when logged in
URL validation : Invalid slugs return a 404 error
Permission checks : Private resume access attempts by non-owners return a 403 error
Slug Generation
Slugs are generated to be unique and URL-safe:
slug : uniqueSlug ( currentUser . email , name )
The uniqueSlug function ensures:
No collisions between different users
URL-safe characters only
Stable slug generation (same name = same slug for same user)
Best Practices
Only make resumes public when you’re actively sharing them. Keep work-in-progress resumes private.
Check your view and download counts to see how much engagement your shared resume is getting.
Ensure your resume is complete and up-to-date before making it public and sharing the link.
Include your public resume URL in job applications, LinkedIn profiles, or email signatures for easy access.
Revoke access when needed
Make your resume private when you’re no longer actively job searching to control who can access it.
Public Resume Viewer
When someone visits your public resume URL, they see a dedicated viewer page:
Full PDF preview of your resume
Download button to save the PDF
No editing capabilities
Clean, distraction-free interface
The viewer automatically increments view counts and tracks downloads, giving you valuable insights into how your resume is being engaged with.