외부 파비콘을 faviconV2 origin 방식으로 수정해 서브도메인 아이콘이 올바르게 표시되도록 한다.

uniare.sori.studio처럼 domain 기반 API가 기본 지구본만 반환하던 문제를 해결하고, Recommended 썸네일 실패 fallback을 추가한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 11:38:30 +09:00
parent 7107d56fc7
commit f206499f99
9 changed files with 72 additions and 17 deletions

View File

@@ -1,4 +1,6 @@
<script setup>
import { getExternalFaviconUrl } from '~/lib/external-favicon-url.js'
const props = defineProps({
url: {
type: String,
@@ -34,13 +36,7 @@ const displayHost = computed(() => {
* 썸네일이 비었을 때 파비콘 보조 URL을 만든다.
* @returns {string} favicon 요청 URL
*/
const faviconUrl = computed(() => {
if (!displayHost.value) {
return ''
}
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(displayHost.value)}&sz=128`
})
const faviconUrl = computed(() => getExternalFaviconUrl(props.url, 128))
/**
* 실제로 표시할 이미지 주소

View File

@@ -55,14 +55,55 @@ const getRecommendedDisplayText = (item) => {
return String(item?.descriptionText || '').trim() || String(item?.url || '').trim()
}
/** 추천 사이트 이미지 로드 실패 단계 (`thumbnail` → `favicon` → `letter`) */
const recommendedImageStage = ref({})
/**
* 추천 사이트 파비콘 URL을 반환한다.
* @param {Object} item - 추천 사이트 항목
* @returns {string} 파비콘 URL
*/
const getRecommendedFaviconUrl = (item) => getExternalFaviconUrl(item?.url, 64)
/**
* 추천 사이트 이미지 URL을 반환한다.
* @param {Object} item - 추천 사이트 항목
* @returns {string} 이미지 URL
*/
const getRecommendedImageUrl = (item) => {
const id = String(item?.id || '')
const stage = recommendedImageStage.value[id] || 'thumbnail'
const thumbnailUrl = String(item?.thumbnailUrl || '').trim()
return thumbnailUrl || getExternalFaviconUrl(item?.url, 64)
const faviconUrl = getRecommendedFaviconUrl(item)
if (stage === 'letter') {
return ''
}
if (stage === 'favicon') {
return faviconUrl
}
return thumbnailUrl || faviconUrl
}
/**
* 추천 사이트 이미지 로드 실패 시 다음 단계로 넘긴다.
* @param {Object} item - 추천 사이트 항목
* @returns {void}
*/
const onRecommendedImageError = (item) => {
const id = String(item?.id || '')
if (!id) {
return
}
const thumbnailUrl = String(item?.thumbnailUrl || '').trim()
const faviconUrl = getRecommendedFaviconUrl(item)
const stage = recommendedImageStage.value[id] || 'thumbnail'
if (stage === 'thumbnail' && thumbnailUrl && faviconUrl) {
recommendedImageStage.value = { ...recommendedImageStage.value, [id]: 'favicon' }
return
}
recommendedImageStage.value = { ...recommendedImageStage.value, [id]: 'letter' }
}
/**
@@ -243,6 +284,7 @@ onMounted(() => {
:alt="item.thumbnailUrl ? item.label : ''"
loading="lazy"
referrerpolicy="no-referrer"
@error="onRecommendedImageError(item)"
>
<span v-else class="px-1 text-center leading-none">{{ (item.label || '?').slice(0, 1) }}</span>
</span>