Implementando Funcionalidades de Perfil de Usuario com Claude Code
Aprenda sobre implementing user profile features usando o Claude Code. Inclui exemplos praticos de codigo.
ユーザープロフィール機能をClaude Codeで構築する
ユーザープロフィールはSNS、SaaS、コミュニティサイトなど多くのアプリに必要な機能です。Claude Codeを使えば、アバターアップロード、プロフィールEdit、公開ページを含む完全な機能を効率的に実装できます。
データモデル
> ユーザープロフィール機能を作って。
> アバター画像のアップロード、表示名・自己紹介・SNSリンクのEdit、
> 公開プロフィールページを実装して。
// src/types/profile.ts
export interface UserProfile {
id: string;
userId: string;
displayName: string;
bio: string;
avatarUrl?: string;
location?: string;
website?: string;
socialLinks: {
twitter?: string;
github?: string;
linkedin?: string;
};
isPublic: boolean;
createdAt: Date;
updatedAt: Date;
}
プロフィール更新API
// src/app/api/profile/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
const profileSchema = z.object({
displayName: z.string().min(1).max(50),
bio: z.string().max(500).optional(),
location: z.string().max(100).optional(),
website: z.string().url().optional().or(z.literal('')),
socialLinks: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
linkedin: z.string().optional(),
}).optional(),
isPublic: z.boolean().optional(),
});
export async function PUT(request: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: '認証が必要です' }, { status: 401 });
}
const body = await request.json();
const validated = profileSchema.parse(body);
const profile = await prisma.profile.upsert({
where: { userId: session.user.id },
update: { ...validated, updatedAt: new Date() },
create: { ...validated, userId: session.user.id },
});
return NextResponse.json(profile);
}
export async function GET(request: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: '認証が必要です' }, { status: 401 });
}
const profile = await prisma.profile.findUnique({
where: { userId: session.user.id },
});
return NextResponse.json(profile);
}
アバターアップロード
// src/app/api/profile/avatar/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { uploadToS3 } from '@/lib/storage';
import sharp from 'sharp';
export async function POST(request: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: '認証が必要です' }, { status: 401 });
}
const formData = await request.formData();
const file = formData.get('avatar') as File;
if (!file) {
return NextResponse.json({ error: 'ファイルが必要です' }, { status: 400 });
}
// ファイルサイズチェック(5MB上限)
if (file.size > 5 * 1024 * 1024) {
return NextResponse.json({ error: 'ファイルサイズは5MB以下にしてください' }, { status: 400 });
}
const buffer = Buffer.from(await file.arrayBuffer());
// 画像をリサイズ・最適化
const optimized = await sharp(buffer)
.resize(256, 256, { fit: 'cover' })
.webp({ quality: 80 })
.toBuffer();
const key = `avatars/${session.user.id}.webp`;
const url = await uploadToS3(optimized, key, 'image/webp');
await prisma.profile.update({
where: { userId: session.user.id },
data: { avatarUrl: url },
});
return NextResponse.json({ avatarUrl: url });
}
プロフィールEditフォーム
// src/components/ProfileForm.tsx
'use client';
import { useState, useRef } from 'react';
import { UserProfile } from '@/types/profile';
export function ProfileForm({ profile }: { profile: UserProfile }) {
const [form, setForm] = useState(profile);
const [saving, setSaving] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('avatar', file);
const res = await fetch('/api/profile/avatar', { method: 'POST', body: formData });
const data = await res.json();
setForm((prev) => ({ ...prev, avatarUrl: data.avatarUrl }));
};
const handleSave = async () => {
setSaving(true);
await fetch('/api/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
});
setSaving(false);
};
return (
<div className="max-w-2xl mx-auto space-y-6">
<div className="flex items-center gap-6">
<div
onClick={() => fileInputRef.current?.click()}
className="w-24 h-24 rounded-full bg-gray-200 overflow-hidden cursor-pointer hover:opacity-80"
>
{form.avatarUrl ? (
<img src={form.avatarUrl} alt="アバター" className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400 text-3xl">+</div>
)}
</div>
<input ref={fileInputRef} type="file" accept="image/*" onChange={handleAvatarChange} className="hidden" />
<div>
<p className="font-medium dark:text-white">プロフィール画像</p>
<p className="text-sm text-gray-500">JPG, PNG, WebP(最大5MB)</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 dark:text-gray-300">表示名</label>
<input
value={form.displayName}
onChange={(e) => setForm({ ...form, displayName: e.target.value })}
className="w-full border rounded-lg px-4 py-2 dark:bg-gray-800 dark:border-gray-700"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 dark:text-gray-300">自己紹介</label>
<textarea
value={form.bio}
onChange={(e) => setForm({ ...form, bio: e.target.value })}
className="w-full border rounded-lg px-4 py-2 h-24 dark:bg-gray-800 dark:border-gray-700"
maxLength={500}
/>
<p className="text-xs text-gray-400 mt-1">{form.bio.length}/500</p>
</div>
<button onClick={handleSave} disabled={saving} className="bg-blue-600 text-white px-6 py-2 rounded-lg disabled:opacity-50">
{saving ? '保存中...' : '保存'}
</button>
</div>
</div>
);
}
関連記事
画像アップロードの詳細はファイルアップロード実装、バリデーションの設計はフォームバリデーションも参考にしてください。
画像処理ライブラリsharpの公式ドキュメント(sharp.pixelplumbing.com)もあわせてご確認ください。
PDF gratuito: Cheatsheet do Claude Code em 5 minutos
Basta informar seu e-mail e enviamos na hora o cheatsheet em uma página A4.
Cuidamos dos seus dados pessoais e nunca enviamos spam.
Sobre o autor
Masa
Engenheiro apaixonado por Claude Code. Mantém o claudecode-lab.com, uma mídia tech em 10 idiomas com mais de 2.000 páginas.
Artigos relacionados
7 verificações antes de publicar todos os dias um artigo multilíngue sobre Claude Code
Uma checklist prática para publicar artigos multilíngues sobre Claude Code todos os dias sem esquecer idiomas, quebrar CTAs ou deixar páginas antigas no ar.
O que e Codex Automations? Conteudo, analise e deploy com IA enquanto voce dorme
Guia pratico para usar Codex Automations em analytics, artigos, CTA, deploy e monetizacao.
Desenhe Firestore com Claude Code: comece pelas consultas
Workflow prático para Firestore com Claude Code: schema orientado por consultas, índices, custos, regras de segurança e TypeScript.