Overview

Learn how to use images and file storage in the starter kit.

Nuxt Starter Kit uses Nuxt Image module to resize and transform your images using the TwicPics CDN.

Image Provider

The starter kit uses TwicPics as the image provider for CDN delivery. TwicPics provides:

  • Automatic image optimization
  • Responsive images
  • Format conversion (WebP, AVIF)
  • Lazy loading support

Configuration

Images are configured in apps/web/nuxt.config.ts:

export default defineNuxtConfig({
  image: {
    provider: 'twicpics',
    twicpics: {
      baseURL: siteConfig.imageProviderUrl
    }
  }
})

The imageProviderUrl is defined in packages/site-config/siteConfig.ts.

Usage

Use the <NuxtImg> component for optimized images:

<template>
  <NuxtImg
    src="landing-page/hero.png"
    alt="Hero image"
    width="1200"
    height="600"
    loading="lazy"
  />
</template>

Static Images

Place static images in the public/ directory:

public/
  ├── landing-page/
  │   ├── hero.png
  │   └── features.png
  └── docs/
      └── screenshots/

Image Optimization Tips

  1. Use appropriate formats: WebP for web, AVIF for modern browsers
  2. Lazy loading: Add loading="lazy" for below-the-fold images
  3. Responsive images: Use sizes and srcset attributes
  4. Compress before upload: Pre-compress large images before adding to repository

File Storage

The starter kit includes file storage functionality through the layer-storage package with AWS S3 integration for handling user uploads.

AWS S3 Configuration

Set up your AWS S3 credentials in your .env file:

NUXT_PRIVATE_AWS_S3_ACCESS_KEY_ID=your-access-key-id
NUXT_PRIVATE_AWS_S3_SECRET_ACCESS_KEY=your-secret-access-key
NUXT_PRIVATE_AWS_S3_BUCKET_NAME=your-bucket-name
NUXT_PRIVATE_AWS_S3_REGION=us-east-1

AWS S3 Setup

  1. Create an S3 Bucket:
    • Go to AWS S3 Console
    • Create a new bucket with a unique name
    • Configure bucket settings (versioning, encryption, etc.)
  2. Configure CORS (if accessing files from browser):
    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
        "AllowedOrigins": ["https://your-domain.com"],
        "ExposeHeaders": []
      }
    ]
    
  3. Create IAM User:
    • Go to IAM Console
    • Create a new user with programmatic access
    • Attach policy with S3 permissions (or use AmazonS3FullAccess for development)
    • Save the Access Key ID and Secret Access Key

For production, use a more restrictive policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket-name/*",
        "arn:aws:s3:::your-bucket-name"
      ]
    }
  ]
}

Storage Layer

The storage functionality is located in packages/layer-storage:

  • Server utilities: Upload, delete, and manage files
  • Database schema: File metadata storage in server/db/schema/file.ts
  • Client composables: File upload utilities

Files are tracked in the database with metadata including file name, path, size, content type, upload timestamp, and associated user ID.

Uploading Files

Server-side:

// server/api/upload.post.ts
import { uploadFileToS3 } from 'layer-storage/server/utils/storage'

export default defineEventHandler(async (event) => {
  const { user } = await requireAuth(event)
  const formData = await readFormData(event)
  const file = formData.get('file') as File

  const result = await uploadFileToS3(file, {
    folder: 'uploads',
    userId: user.id
  })

  return result
})

Client-side:

<script setup lang="ts">
const file = ref<File | null>(null)
const uploading = ref(false)

async function handleUpload () {
  if (!file.value)
    return

  uploading.value = true
  const formData = new FormData()
  formData.append('file', file.value)

  try {
    await $fetch('/api/upload', {
      body: formData,
      method: 'POST'
    })
    // Handle success
  }
  catch (error) {
    // Handle error
  }
  finally {
    uploading.value = false
  }
}
</script>

<template>
  <div>
    <input
      type="file"
      @change="file = $event.target.files[0]"
    >
    <button
      :disabled="uploading"
      @click="handleUpload"
    >
      {{ uploading ? 'Uploading...' : 'Upload' }}
    </button>
  </div>
</template>

File Access

Files stored in S3 can be accessed via:

  1. Direct S3 URLs - For public files
  2. Signed URLs - For private files with temporary access
  3. CloudFront CDN - For optimized delivery (recommended)

Generate signed URLs for private files:

import { getSignedUrl } from 'layer-storage/server/utils/storage'

const signedUrl = await getSignedUrl(fileKey, {
  expiresIn: 3600 // 1 hour
})

Storage Best Practices

  1. Validate file types - Only allow expected file types
  2. Limit file sizes - Set appropriate size limits
  3. Scan for malware - Implement virus scanning for user uploads
  4. Use signed URLs - For private files, always use signed URLs
  5. Organize files - Use consistent folder structures (e.g., uploads/{userId}/{filename})
  6. Set lifecycle policies - Automatically delete or archive old files
  7. Enable versioning - Protect against accidental deletions
  8. Monitor costs - Set up CloudWatch alerts for S3 usage

Alternative Storage Providers

While the starter kit uses AWS S3 by default, you can integrate other S3-compatible storage providers:

  • Cloudflare R2 - S3-compatible with no egress fees
  • DigitalOcean Spaces - S3-compatible object storage
  • Backblaze B2 - Cost-effective S3-compatible storage
  • Google Cloud Storage - Google's object storage solution

To use an alternative provider, update the storage utilities in packages/layer-storage/server/utils/storage.ts with the provider's SDK and configuration.