Chuyển đến nội dung chính

レッスン 15: ファイルのアップロード、画像、SEO

ファイルアップロード処理、S3ストレージ。 @nuxt/image、レスポンシブ画像、遅延読み込み。 useSeoMeta、useHeadによるSEO。サイトマップ、OG タグ、構造化データ (JSON-LD)。

💻 プログラミング — レッスン 15 レッスン 15: ファイルのアップロード、画像、SEO

Vue.js と Nuxt: 基本から高度まで

パート 4: Nuxt アドバンストおよびフルスタック

xdev.asia

1. ファイルのアップロード

// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
  const files = await readMultipartFormData(event)
  if (!files || files.length === 0) {
    throw createError({ statusCode: 400, message: 'No file uploaded' })
  }

  const file = files[0]
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']

  if (!allowedTypes.includes(file.type || '')) {
    throw createError({ statusCode: 400, message: 'Invalid file type' })
  }

  if (file.data.length > 5 * 1024 * 1024) {
    throw createError({ statusCode: 400, message: 'File too large (max 5MB)' })
  }

  const filename = `${Date.now()}-${file.filename}`

  // NuxtHub Blob (Cloudflare R2)
  const blob = await hubBlob().put(filename, file.data, {
    contentType: file.type,
  })

  return { url: blob.pathname }
})

2. @nuxt/image

<template>
  <!-- Basic -->
  <NuxtImg
    src="/images/hero.jpg"
    alt="Hero"
    width="800"
    height="400"
    loading="lazy"
    format="webp"
  />

  <!-- Responsive sizes -->
  <NuxtImg
    src="/images/banner.jpg"
    sizes="sm:100vw md:50vw lg:400px"
    quality="80"
    placeholder
  />

  <!-- Picture element with multiple formats -->
  <NuxtPicture
    src="/images/photo.jpg"
    format="avif,webp"
    sizes="sm:100vw md:50vw"
    quality="80"
  />
</template>

3. useSeoMeta による SEO

<script setup lang="ts">
// Static SEO
useSeoMeta({
  title: 'Trang chủ | xDev',
  ogTitle: 'xDev - Nền tảng học lập trình',
  description: 'Học lập trình miễn phí với các khóa học chất lượng',
  ogDescription: 'Học lập trình miễn phí',
  ogImage: '/images/og-default.jpg',
  twitterCard: 'summary_large_image',
})
</script>
<!-- Dynamic SEO -->
<script setup lang="ts">
const { data: post } = await useFetch(`/api/posts/${slug}`)

useSeoMeta({
  title: () => post.value?.title,
  ogTitle: () => post.value?.title,
  description: () => post.value?.description,
  ogImage: () => post.value?.image,
  ogType: 'article',
  articlePublishedTime: () => post.value?.createdAt,
})

// useHead for full control
useHead({
  script: [
    {
      type: 'application/ld+json',
      textContent: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: post.value?.title,
        image: post.value?.image,
        datePublished: post.value?.createdAt,
      }),
    },
  ],
})
</script>

4. サイトマップの生成

npx nuxi module add @nuxtjs/sitemap
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  sitemap: {
    sources: ['/api/__sitemap__/urls'],
  },
  site: {
    url: 'https://blog.xdev.asia',
  },
})

// server/api/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
  const posts = await db.select({ slug: schema.posts.slug, updatedAt: schema.posts.updatedAt })
    .from(schema.posts)
    .where(eq(schema.posts.published, true))

  return posts.map(post => ({
    loc: `/blog/${post.slug}`,
    lastmod: post.updatedAt,
    priority: 0.7,
  }))
})

次の記事: リアルタイム、WebSocket、i18n — リアルタイムのインタラクションと多言語。