nuxt-hub / core

Build full-stack applications with Nuxt on CloudFlare, with zero configuration.
https://hub.nuxt.com
Apache License 2.0
993 stars 57 forks source link

413 Payload Too Large Error on Uploading Images Larger Than 2MB Using NuxtHub with Cloudflare #364

Closed tahirmahmudzade closed 2 weeks ago

tahirmahmudzade commented 2 weeks ago

Describe the bug I’m encountering a 413 Payload Too Large error when attempting to upload images larger than 2MB in a Nuxt project using NuxtHub, deployed via Cloudflare. This issue persists even though Cloudflare’s free tier supposedly supports larger uploads to R2 storage (up to 100 MB via API). Additionally, I have configured the requestSizeLimiter in Nuxt Security with limits far above 2MB, and even turned it off completely as a troubleshooting step, but the error persists.

Steps to reproduce Steps to reproduce the behavior:

  1. Set up a Nuxt 3 project using NuxtHub with Cloudflare for deployment.
  2. Attempt to upload an image file larger than 2MB to R2 storage through NuxtHub.
  3. Observe that the upload fails with a 413 Payload Too Large error.

Expected behavior The image upload should succeed for files up to Cloudflare’s free-tier limit (up to 100 MB via API for R2). The configuration in Nuxt Security should allow higher upload limits, so files up to the specified limits should upload without errors.

Current Configuration

import pkg from './package.json'
import { canonicalUrl } from './utils/const'

export default defineNuxtConfig({
  extends: ['@nuxt/ui-pro'],
  modules: [
    '@nuxt/ui',
    '@nuxt/image',
    '@nuxthub/core',
    '@nuxt/icon',
    'nuxt-auth-utils',
    'nuxt-typed-router',
    '@pinia/nuxt',
    'pinia-plugin-persistedstate/nuxt',
    '@vueuse/nuxt',
    // 'nuxt-security',
    '@nuxtjs/i18n',
  ],

  // requestSizeLimiter: { maxRequestSizeInBytes: 9000000, maxUploadFileRequestInBytes: 90000000, throwError: false },
  // security: {
  //   requestSizeLimiter: false,
  //   headers: {
  //     crossOriginEmbedderPolicy: 'unsafe-none',
  //     contentSecurityPolicy: {
  //       'img-src': ["'self'", 'data:', 'blob:', 'https:', 'https://*.googleusercontent.com'],
  //       'script-src': ["'self'", 'https:', "'strict-dynamic'", "'nonce-{{nonce}}'"],
  //       'script-src-attr': ["'self'", "'nonce-{{nonce}}'"],
  //     },
  //     xXSSProtection: '1; mode=block',
  //   },
  // },
  // nitro: {
  //   experimental: { websocket: true },
  //   routeRules: {
  //     '/api/auth/reset-password': { security: { rateLimiter: { tokensPerInterval: 6, interval: 60000, throwError: true } } },
  //     '/api/auth/login': { security: { rateLimiter: { tokensPerInterval: 6, interval: 60000, throwError: true } } },
  //   },
  // },

  experimental: { payloadExtraction: true, appManifest: true },

  app: { pageTransition: { name: 'page', mode: 'out-in' }, layoutTransition: { name: 'layout', mode: 'out-in' } },

  css: ['~/assets/css/transitions.css'],
  colorMode: { preference: 'dark', fallback: 'dark' },

  ui: { global: true },
  image: { dir: 'public', format: ['webp', 'avif'], domains: ['schmalify.com'] },
  icon: { clientBundle: { scan: true, sizeLimitKb: 256 } },

  runtimeConfig: {
    mailgunApiKey: process.env.MAILGUN_API_KEY,
    mailgunDomain: process.env.MAILGUN_DOMAIN,
    mailgunFromEmail: process.env.MAILGUN_FROM_EMAIL,
    jwtSecret: process.env.JWT_SECRET,
    oauth: { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } },

    public: { siteUrl: process.env.NUXT_PUBLIC_SITE_URL, canonicalUrl, buildDate: new Date(), appName: pkg.name },
  },

  i18n: {
    detectBrowserLanguage: { useCookie: true, fallbackLocale: 'en' },
    strategy: 'no_prefix',
    langDir: 'internationalization',
    defaultLocale: 'en',
    lazy: true,
    locales: [
      { code: 'en', name: 'English', file: 'en.json' },
      { code: 'de', name: 'Deutsch', file: 'de.json' },
    ],
  },

  hub: { database: true, blob: true, kv: true, remote: 'production' },

  devtools: { enabled: true },
  imports: { dirs: ['composables/**'] },
  vue: { propsDestructure: true },
  future: { compatibilityVersion: 4 },
  compatibilityDate: '2024-09-25',
})

To troubleshoot, I turned off the request size limiter entirely, but the error persisted with the same 413 Payload Too Large response

for (let i = 0; body.has(`image_${i}`); i++) {
      const image = body.get(`image_${i}`) as File

      const processedImageName = processImage(image) // Process each image

      try {
        console.log('image', image)
        console.log('image size in mb', image.size / 1024 / 1024)

        await hubBlob().put(processedImageName, image, { prefix: `${user.id}/items` })
      } catch (err) {
        console.log('Error uploading image', err)
        throw createError({ statusCode: 500, message: 'Error uploading image' })
      }

      images.push(processedImageName) // Add the image name to the array
    }

This is the part of the api that i'm uploading the files

categoryId aHZpSVkx
image File { size: 2601755,
  type: 'image/jpeg',
  name: 'jakob-owens-518ucsWH1DQ-unsplash(2).jpg',
  lastModified: 1730709471745 }
image size in mb 2.481226921081543
Error uploading image [PUT] "https://schmalify.com/api/_hub/blob/jakob-owens-518ucswh1dq-unsplash(2).jpg?prefix=SkVnQVlo%2Fitems": 413 Payload Too Large

  at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
  at async $fetch2 (node_modules/.pnpm/ofetch@1.4.0/node_modules/ofetch/dist/shared/ofetch.4747642d.mjs:304:15)
  at async Object.put (node_modules/.pnpm/@nuxthub+core@0.7.24-20241002-182519-780ff7d_ioredis@5.4.1_magicast@0.3.5_rollup@4.24.0_vite@_c7vdh6mufcjizml67seor43usy/node_modules/@nuxthub/core/dist/runtime/blob/server/utils/blob.js:200:14)
  at async Object.handler (server/api/items/index.post.ts:42:1)
  at async node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:1975:19
  at async Object.callAsync (node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
  at async Object.callAsync (node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
  at async Server.toNodeHandle (node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:2266:7)
Error:  Error uploading image

  at createError (node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:78:15)
  at Object.handler (server/api/items/index.post.ts:45:1)
  at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
  at async node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:1975:19
  at async Object.callAsync (node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
  at async Object.callAsync (node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
  at async Server.toNodeHandle (node_modules/.pnpm/h3@1.12.0/node_modules/h3/dist/index.mjs:2266:7)

Questions

  1. Is there a configuration setting in NuxtHub or Cloudflare that needs adjusting to support larger uploads?
  2. Are there additional best practices for handling large file uploads in NuxtHub with Cloudflare that could avoid this issue?
atinux commented 2 weeks ago

I am not sure to understand what is happening on your side, I deployed the nuxthub starter and it works with an image of 7MB on the Workers free plan:

https://github.com/user-attachments/assets/f4332724-b890-45cf-a245-2f50343f5af4

It also works when uploading from the NuxtHub admin:

https://github.com/user-attachments/assets/0c0cff06-be63-48b4-85b2-3bced9d99aed

tahirmahmudzade commented 2 weeks ago

I am not sure to understand what is happening on your side, I deployed the nuxthub starter and it works with an image of 7MB on the Workers free plan:

CleanShot.2024-11-04.at.17.41.30.mp4 It also works when uploading from the NuxtHub admin:

CleanShot.2024-11-04.at.17.43.03.mp4

thanks a lot for your attention. I'm not sure why but it was only a problem in local dev nuxt dev. I was able to upload images even more than 5mb on my production website, but i was getting 413 Payload Too Large specifically on my dev server. After redeployment it all went back to normal. I'm closing this issue as my issue was resolved but still not sure what the problem was.