Skip to content

Cannot get final name for export 'toBase64' @smithy/[email protected] in NextJS #6686

@MinhOmega

Description

@MinhOmega

Checkboxes for prior research

Describe the bug

I am facing an issue when integrating media uploads to S3 using presigned URLs. I have installed @aws-sdk/[email protected] and @aws-sdk/[email protected], and I am using Next.js version 14.2.3. It works fine in development mode, but when I build the application, both locally and on Vercel, it throws the following error:

./node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-stream/dist-es/index.js + 29 modules
Cannot get final name for export 'toBase64' of ./node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-base64/dist-es/index.js

To resolve this, I tried downgrading to version 3.485.0, and it is now working fine.

Regression Issue

  • Select this option if this issue appears to be a regression.

SDK version number

@aws-sdk/[email protected], @aws-sdk/[email protected]

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v20.9.0

Reproduction Steps

I have used server action to create the presigned URL then put the image with that response, here is sample code:

media.ts:

'use server'

import { getNextAuthServerSession } from '@/lib/auth'
import env from '@/lib/env'
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { nanoid } from 'nanoid'

// Initialize S3 client
const s3Client = new S3Client({
  region: env.AWS_REGION,
  credentials: {
    accessKeyId: env.AWS_ACCESS_KEY_ID,
    secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
  },
})

export interface PresignedUrlResponse {
  uploadUrl: string
  fileKey: string
  error?: string
}

const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB

export const getPresignedUrl = async (
  fileName: string,
  fileType: string,
  directory: string = 'uploads'
): Promise<PresignedUrlResponse> => {
  try {
    // Check authentication (can ignore that and hard code for example)
    const session = await getNextAuthServerSession()
    if (!session?.user?.id) {
      return { uploadUrl: '', fileKey: '', error: 'Unauthorized' }
    }

    // Validate file type
    if (!ALLOWED_FILE_TYPES.includes(fileType)) {
      return {
        uploadUrl: '',
        fileKey: '',
        error: `Invalid file type. Allowed types: ${ALLOWED_FILE_TYPES.join(', ')}`,
      }
    }

    // Clean the filename and get extension
    const cleanFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '-').toLowerCase()
    const fileExtension = cleanFileName.split('.').pop()

    if (!fileExtension) {
      return { uploadUrl: '', fileKey: '', error: 'Invalid file extension' }
    }

    // Generate a unique file key
    const uniqueId = nanoid()
    const fileKey = `${directory}/${uniqueId}-${cleanFileName}`

    // Create the presigned URL
    const putObjectCommand = new PutObjectCommand({
      Bucket: env.AWS_BUCKET_NAME,
      Key: fileKey,
      ContentType: fileType,
      // Add additional metadata
      Metadata: {
        uploadedBy: session.user.id,
        originalName: fileName,
      },
    })

    const uploadUrl = await getSignedUrl(s3Client, putObjectCommand, {
      expiresIn: 60 * 5, // URL expires in 5 minutes
      signableHeaders: new Set(['content-type']), // Only sign necessary headers
    })

    return {
      uploadUrl,
      fileKey,
    }
  } catch (error) {
    console.error('Error generating presigned URL:', error)
    return {
      uploadUrl: '',
      fileKey: '',
      error: 'Failed to generate upload URL',
    }
  }
}

export const getPublicUrl = async (fileKey: string): Promise<string> => {
  if (!fileKey) return ''

  // Clean the file key to prevent directory traversal
  const cleanFileKey = fileKey.replace(/^\/+/, '').replace(/\.{2,}/g, '.')
  return `${env.AWS_CLOUDFRONT_URL}/${cleanFileKey}`
}

// Helper function to validate file size
export const validateFileSize = async (size: number): Promise<boolean> => {
  return size <= MAX_FILE_SIZE
}

// Helper function to validate file type
export const validateFileType = async (type: string): Promise<boolean> => {
  return ALLOWED_FILE_TYPES.includes(type)
}

in the upload in client side:

// Get presigned URL for the upload
  const { uploadUrl, fileKey, error } = await getPresignedUrl(file.name, file.type, 'slider-images')
  
  if (error || !uploadUrl) {
    console.error('Failed to get upload URL')
    continue
  }
  
  // Upload the file directly to S3
  const uploadResponse = await fetch(uploadUrl, {
    method: 'PUT',
    body: file,
    headers: {
      'Content-Type': file.type,
    },
    mode: 'cors',
  })

Observed Behavior

./node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-stream/dist-es/index.js + 29 modules
Cannot get final name for export 'toBase64' of ./node_modules/.pnpm/@[email protected]/node_modules/@smithy/util-base64/dist-es/index.js

Expected Behavior

Build the project success without any error same as version 3.485.0

Possible Solution

No response

Additional Information/Context

No response

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.p2This is a standard priority issuepotential-regressionMarking this issue as a potential regression to be checked by team member

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions