← Back to Blog

Using Next.js & Node.js AWS SDK (@aws-sdk/client-s3) with QuixiCloud Storage: Complete 2026 Guide

By QuixiCloud Team•October 17, 2026
Using Next.js & Node.js AWS SDK (@aws-sdk/client-s3) with QuixiCloud Storage: Complete 2026 Guide

Using Next.js & Node.js AWS SDK (@aws-sdk/client-s3) with QuixiCloud Storage: Complete 2026 Guide

The JavaScript and TypeScript ecosystem dominates modern web development in India. Frameworks like Next.js (built by Vercel) and standalone Node.js backends power the digital user experiences of India's leading startups, e-commerce storefronts, and SaaS platforms.

When a Next.js application needs to handle file uploads—such as user avatars, KYC identity documents, or digital media assets—the modern industry standard is the modular AWS SDK for JavaScript v3 (@aws-sdk/client-s3).

Because QuixiCloud M-S3 strictly implements standard AWS S3 API specifications, you can use the official modular AWS SDK v3 without installing third-party wrapper packages.

In this practical technical guide, we walk through configuring the AWS SDK v3 in a Next.js 14/15 App Router project, implementing direct-to-S3 presigned uploads with Server Actions, and rendering uploaded assets with blazing-fast Mumbai latency.


1. Installing the Required Dependencies

In your Next.js project root, install the modular AWS SDK v3 client and request presigner packages:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

2. Configuring Environment Variables

Create or update your .env.local file with your QuixiCloud M-S3 credentials:

# .env.local
QUIXICLOUD_S3_ENDPOINT=https://s3.quixicloud.com
QUIXICLOUD_ACCESS_KEY_ID=qx_live_YOUR_ACCESS_KEY
QUIXICLOUD_SECRET_ACCESS_KEY=qx_sec_YOUR_SECRET_KEY
QUIXICLOUD_BUCKET_NAME=my-app-production-assets
QUIXICLOUD_REGION=in-west-1

Security Tip: Never prefix sensitive S3 access keys with NEXT_PUBLIC_. Keep them strictly accessible only on the server side to protect your infrastructure.


3. Creating a Singleton S3 Client Instance

Create a dedicated utility module to initialize a single, reusable S3 client instance:

// src/lib/s3Client.ts
import { S3Client } from "@aws-sdk/client-s3";

if (!process.env.QUIXICLOUD_ACCESS_KEY_ID || !process.env.QUIXICLOUD_SECRET_ACCESS_KEY) {
  throw new Error("Missing QuixiCloud S3 environment variables.");
}

export const s3Client = new S3Client({
  endpoint: process.env.QUIXICLOUD_S3_ENDPOINT || "https://s3.quixicloud.com",
  region: process.env.QUIXICLOUD_REGION || "in-west-1",
  credentials: {
    accessKeyId: process.env.QUIXICLOUD_ACCESS_KEY_ID,
    secretAccessKey: process.env.QUIXICLOUD_SECRET_ACCESS_KEY,
  },
  forcePathStyle: true, // Recommended for custom S3 endpoints
});

4. Implementing Next.js Server Action for Presigned Uploads

With Next.js App Router, the cleanest architecture is generating a temporary Presigned URL on the server, which the browser then uses to upload the file directly to QuixiCloud M-S3 via HTTP PUT.

Create a Server Action in src/app/actions/upload.ts:

// src/app/actions/upload.ts
"use server";

import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3Client } from "@/lib/s3Client";
import { v4 as uuidv4 } from "uuid";

export async function getPresignedUploadUrl(filename: string, fileType: string) {
  try {
    const fileExtension = filename.split(".").pop();
    const uniqueKey = `uploads/${uuidv4()}.${fileExtension}`;

    const command = new PutObjectCommand({
      Bucket: process.env.QUIXICLOUD_BUCKET_NAME,
      Key: uniqueKey,
      ContentType: fileType,
    });

    // Generate signed URL valid for 5 minutes (300 seconds)
    const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 });

    return {
      success: true,
      uploadUrl: presignedUrl,
      fileKey: uniqueKey,
    };
  } catch (error) {
    console.error("Error generating presigned URL:", error);
    return { success: false, error: "Failed to generate upload URL." };
  }
}

5. Building the Client-Side Upload Component

Now build a clean React component in src/components/FileUpload.tsx that triggers the Server Action and pushes the binary file directly to QuixiCloud:

// src/components/FileUpload.tsx
"use client";

import { useState } from "react";
import { getPresignedUploadUrl } from "@/app/actions/upload";

export default function FileUpload() {
  const [file, setFile] = useState<File | null>(null);
  const [uploading, setUploading] = useState(false);
  const [uploadedKey, setUploadedKey] = useState<string | null>(null);

  const handleUpload = async () => {
    if (!file) return;
    setUploading(true);

    try {
      // 1. Request presigned upload URL from Next.js server action
      const res = await getPresignedUploadUrl(file.name, file.type);
      if (!res.success || !res.uploadUrl) {
        throw new Error(res.error || "Failed to retrieve upload URL");
      }

      // 2. Upload file directly from browser to QuixiCloud M-S3
      const uploadResponse = await fetch(res.uploadUrl, {
        method: "PUT",
        headers: { "Content-Type": file.type },
        body: file,
      });

      if (!uploadResponse.ok) {
        throw new Error("Direct upload to S3 failed.");
      }

      setUploadedKey(res.fileKey || null);
      alert("Uploaded successfully to sovereign QuixiCloud storage!");
    } catch (err: any) {
      alert("Upload failed: " + err.message);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div className="p-6 border rounded-xl max-w-md bg-white shadow-sm">
      <h3 className="text-lg font-bold mb-4">Direct Next.js S3 Upload</h3>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files?.[0] || null)}
        className="mb-4 block w-full text-sm text-gray-500"
      />
      <button
        onClick={handleUpload}
        disabled={!file || uploading}
        className="px-4 py-2 bg-indigo-600 text-white font-semibold rounded-lg disabled:opacity-50"
      >
        {uploading ? "Uploading..." : "Upload to QuixiCloud"}
      </button>

      {uploadedKey && (
        <p className="mt-4 text-sm text-green-600">
          Saved Key: <code className="bg-gray-100 p-1 rounded">{uploadedKey}</code>
        </p>
      )}
    </div>
  );
}

6. Configuring next.config.js for Fast Image Optimization

If you render uploaded user images using Next.js’s <Image /> component, whitelist the QuixiCloud S3 domain in your next.config.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.quixicloud.com',
        pathname: '/**',
      },
    ],
  },
};

module.exports = nextConfig;

Why Next.js Developers Choose QuixiCloud M-S3 in India

  1. Zero Bandwidth Bill Shock: With 1.5× to 3.0× included egress bandwidth, your Next.js application will never trigger thousands of dollars in surprise data transfer fees.
  2. Sub-15ms Domestic Latency: Storing assets in Mumbai and Noida ensures that users across Bangalore, Delhi, Hyderabad, and Mumbai experience lightning-fast asset loading.
  3. Transparent INR Pricing: Pay in Indian Rupees starting at ₹399/month for 1 TB with complete 18% GST input credit invoices.

Power your Next.js applications with predictable, high-performance object storage. Deploy your bucket today at aws.quixicloud.com.

Q

Written by QuixiCloud Team

The QuixiCloud India Engineering Team is dedicated to building secure, scalable, and privacy-first cloud storage solutions tailored for Indian businesses and users.