Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/icons/mobile-thread.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/thread.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions src/app/(main)/teampsylog/head/[uuid]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,49 @@ import React from 'react';
import KeywordCard from './_components/KeywordCard';
import Button from '@/components/common/Button';
import Link from 'next/link';
import axios from 'axios';
import { Metadata } from 'next';

type Props = {
params: Promise<{ uuid: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { uuid } = await params;
let userName = '사용자';

try {
const res = await axios.get(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/teamficial-log/requester`,
{
params: {
requesterUuid: uuid,
},
},
);
Comment on lines +17 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

환경 변수 및 요청 타임아웃 설정을 확인하세요.

  1. NEXT_PUBLIC_ 접두사가 붙은 환경 변수는 클라이언트에 노출됩니다. generateMetadata는 서버에서만 실행되므로 서버 전용 환경 변수(예: API_BASE_URL) 사용을 권장합니다.
  2. 타임아웃이 설정되지 않아 API 응답 지연 시 페이지 렌더링이 블로킹될 수 있습니다.
🔧 타임아웃 추가 예시
     const res = await axios.get(
       `${process.env.NEXT_PUBLIC_API_BASE_URL}/teamficial-log/requester`,
       {
         params: {
           requesterUuid: uuid,
         },
+        timeout: 5000,
       },
     );
🤖 Prompt for AI Agents
In `@src/app/`(main)/teampsylog/head/[uuid]/page.tsx around lines 17 - 24, The
code is using a client-exposed env var and no request timeout; update the axios
call in page.tsx (where generateMetadata/server-side logic runs) to use the
server-only environment variable process.env.API_BASE_URL instead of
process.env.NEXT_PUBLIC_API_BASE_URL, and add a timeout to the request (e.g.,
via the axios config timeout option or by creating an axios instance with a
default timeout) so the GET to /teamficial-log/requester with params {
requesterUuid: uuid } fails fast on slow responses.


userName = res.data?.result?.requesterName ?? '사용자';
} catch (error) {
console.error('Failed to fetch requester data:', error);
}

return {
title: `${userName}님의 팀피셜록을 작성해볼까요?`,
description: `소프트스킬 팀빌딩 서비스, 팀피셜`,
openGraph: {
title: `${userName}님의 팀피셜록을 작성해볼까요?`,
description: `소프트스킬 팀빌딩 서비스, 팀피셜`,
images: [
{
url: '/og/Teamficial_metatag_Image.jpg',
width: 1200,
height: 630,
},
],
Comment on lines +37 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

OG 이미지 URL은 절대 경로로 지정해야 합니다.

소셜 미디어 크롤러(Facebook, Twitter 등)는 상대 경로를 인식하지 못합니다. metadataBase를 설정하거나 전체 URL을 직접 명시해야 합니다.

🐛 수정 방안

방안 1: 절대 URL 직접 사용

       images: [
         {
-          url: '/og/Teamficial_metatag_Image.jpg',
+          url: `${process.env.NEXT_PUBLIC_BASE_URL}/og/Teamficial_metatag_Image.jpg`,
           width: 1200,
           height: 630,
         },
       ],

방안 2: metadataBase 설정 (layout.tsx에서)

// layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://your-domain.com'),
};
🤖 Prompt for AI Agents
In `@src/app/`(main)/teampsylog/head/[uuid]/page.tsx around lines 37 - 43, The OG
image URL in the metadata images array is currently a relative path
('/og/Teamficial_metatag_Image.jpg') which social crawlers don't resolve; update
the images entry in the metadata object in page.tsx to use an absolute URL
(e.g., https://your-domain.com/og/Teamficial_metatag_Image.jpg) or alternatively
set metadataBase in your root layout (export const metadata with metadataBase:
new URL('https://your-domain.com')) so the existing relative path resolves
correctly; modify either the images URL in page.tsx or add metadataBase in
layout.tsx accordingly.

},
};
}

const page = async ({ params }: Props) => {
const { uuid } = await params;

Expand Down
56 changes: 41 additions & 15 deletions src/components/common/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Image from 'next/image';
const Footer = () => {
return (
<>
{/* 모바일 */}
<footer className="desktop:hidden">
<div className="-mx-4 flex flex-col gap-4 bg-gray-200 py-6">
<div className="body-9 flex justify-between px-4">
Expand All @@ -22,28 +23,45 @@ const Footer = () => {
<a>서비스이용약관</a>
</div>
</div>
<div className="flex justify-between px-4">
<div className="flex items-center justify-between px-4">
<div>
<p className="body-11 text-gray-500">Teamficial@gmail.com</p>
<p className="body-11 text-gray-500">© 2025.Teamficial. ALL rights reserved.</p>
</div>
<a
href="https://www.instagram.com/teamficial.official/"
target="_blank"
rel="noopener noreferrer"
aria-label="인스타그램"
>
<Image
src="/icons/mobile-instagram.svg"
alt="instagram"
width={24}
height={24}
className="justify-end"
/>
</a>
<div className="flex gap-2">
<a
href="https://www.instagram.com/teamficial.official/"
target="_blank"
rel="noopener noreferrer"
aria-label="인스타그램"
>
<Image
src="/icons/mobile-instagram.svg"
alt="instagram"
width={24}
height={24}
className="justify-end"
/>
</a>
<a
href="https://www.threads.com/@teamficial.official"
target="_blank"
rel="noopener noreferrer"
aria-label="쓰레드"
>
<Image
src="/icons/mobile-thread.svg"
alt="thread"
width={24}
height={24}
className="justify-end"
/>
</a>
Comment on lines +32 to +59
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd "Footer.tsx" --type f

Repository: TEAMFICIAL/teamficial-fe

Length of output: 162


🌐 Web query:

Next.js Image component aria-hidden attribute support

💡 Result:

Yes. next/image forwards “other properties” to the underlying <img> element (it only excludes srcSet and decoding), so aria-hidden="true" is supported and will end up on the rendered <img>. [1]

For decorative images, accessibility guidance is to use an empty alt (alt="") so screen readers ignore it; you can add aria-hidden="true" as an extra safeguard, but alt is still required by next/image. [1][2][3]

import Image from "next/image";

<Image
  src="/decorative.png"
  width={200}
  height={200}
  alt=""
  aria-hidden="true"
/>

Sources: [1] Next.js <Image> API (“Other Props” forwarding) • [2] W3C WAI decorative images (use alt="") • [3] MDN aria-hidden behavior


🌐 Web query:

decorative images accessibility best practices alt empty string aria-hidden

💡 Result:

Decorative images: what to do (and when)

1) For a purely decorative <img> (no meaning, no function): use a null alt

<img src="..." alt="">
  • This marks the image as decorative so screen readers ignore it.
  • Do not omit alt (missing alt can cause some AT to announce the filename).
  • Make sure it’s exactly empty—no spaces like alt=" " (some AT may still announce it).
    Sources: WebAIM on decorative images and alt="" [1], W3C Images tutorial (decorative) [2], W3C tips re: no space in empty alt [3].

2) Prefer CSS for purely visual styling when possible

  • If it’s only for decoration/layout (borders, flourishes, background textures), use a CSS background image instead of <img> to keep it out of the semantic content.
    Sources: WebAIM [1], HTML Standard guidance [4].

3) role="presentation" / role="none": usually unnecessary for <img>

  • An <img> with alt="" is already treated as decorative in accessibility mappings; adding role="presentation"/none is typically redundant.
    Source: W3C ACT rule describing “marked as decorative” via alt="" or presentational roles [5].

4) aria-hidden="true": use sparingly, and don’t use it to “fix” missing alt

  • aria-hidden="true" removes content from the accessibility tree, which is a different intent than “this image is decorative.”
  • Avoid putting aria-hidden="true" on anything focusable/interactive (it can create confusing keyboard/AT behavior).
    Source: W3C ACT notes on programmatically hidden content and focusable elements [6].

Practical rule of thumb

  • Decorative <img>alt="" (best default).
  • Decorative pure styling → CSS background (best if feasible).
  • Only use aria-hidden="true" when you intentionally want something not exposed to AT at all (and ensure it’s not focusable/interactive).

References

  1. WebAIM – Alternative Text (Decorative images use alt="") [1]
  2. W3C WAI Images Tutorial – Decorative images (alt="", don’t omit alt) [2]
  3. W3C WAI Images Tips – empty alt must not contain spaces [3]
  4. HTML Standard – decorative/supplemental images and empty alt, CSS for site-wide decoration [4]
  5. W3C ACT – “marked as decorative” includes img with alt="" or role none/presentation [5]
  6. W3C ACT – cautions about aria-hidden and focusable elements [6]

🏁 Script executed:

cat -n src/components/common/Footer.tsx | head -100

Repository: TEAMFICIAL/teamficial-fe

Length of output: 4501


이미지 alt 속성 수정 필요 - 중복 낭독 방지

앵커에 aria-label이 있으므로 이미지는 장식 요소로 처리해야 합니다. alt=""만 사용하면 스크린리더가 무시하므로 중복 낭독을 피할 수 있습니다.

♿ 제안 수정
                 <Image
                   src="/icons/mobile-instagram.svg"
-                  alt="instagram"
+                  alt=""
                   width={24}
                   height={24}
                   className="justify-end"
                 />
                 <Image
                   src="/icons/mobile-thread.svg"
-                  alt="thread"
+                  alt=""
                   width={24}
                   height={24}
                   className="justify-end"
                 />
                 <Image src="/icons/mail.svg" 
-                  alt="mail" 
+                  alt=""
                   width={24} height={24} />
                 <Image src="/icons/instagram.svg" 
-                  alt="instagram" 
+                  alt=""
                   width={24} height={24} />
                 <Image src="/icons/thread.svg" 
-                  alt="thread" 
+                  alt=""
                   width={24} height={24} />
🤖 Prompt for AI Agents
In `@src/components/common/Footer.tsx` around lines 32 - 59, In the Footer
component update the two Image elements for the social links (the Instagram and
Thread images) to be treated as decorative by screen readers: set their alt
attribute to an empty string (alt="") and also add aria-hidden="true" or
role="presentation" to the Image elements so the screen reader ignores them and
avoids duplicate announcements with the anchor's aria-label.

</div>
</div>
</div>
</footer>
{/* 데스크탑 */}
<footer className="desktop:flex mx-auto hidden w-full justify-center bg-gray-600 px-10 pb-10">
<div className="flex w-[944px] items-center justify-between">
<div className="body-6 flex flex-col text-gray-100">
Expand All @@ -64,6 +82,14 @@ const Footer = () => {
>
<Image src="/icons/instagram.svg" alt="instagram" width={24} height={24} />
</a>
<a
href="https://www.threads.com/@teamficial.official"
target="_blank"
rel="noopener noreferrer"
aria-label="쓰레드"
>
<Image src="/icons/thread.svg" alt="thread" width={24} height={24} />
</a>
</div>
<div className="body-7 flex gap-4 text-gray-100">
<a
Expand Down