AWS SDK for JavaScript v3は、Node.jsやブラウザからAWSのAPIを呼ぶための公式ライブラリです。v2(npmパッケージ名 aws-sdk)は2025年9月8日にサポートが終了しており、いま新規に書くコードも、既存コードの移行先もv3一択です。
この記事では、v3の導入からS3・DynamoDBの基本操作、v2からの移行手順までを、ローカルで実行した結果を添えて説明します。移行ツールで変換しても直らない箇所、S3互換ストレージで突然アップロードが失敗する既定値の変更など、公式ガイドを順に読むだけでは気づきにくい落とし穴を中心に扱います。実行環境はNode.js v26.5.0、@aws-sdk/client-s3 3.1132.0(2026年9月15日時点のnpm最新)です。
まとめ:v3で押さえるべき要点
- v2(aws-sdk)は2024年9月8日にメンテナンスモード入り、2025年9月8日にサポート終了。npmでインストールすると非推奨の警告が出ます。
- v3はサービスごとに @aws-sdk/client-s3 のような個別パッケージを入れ、クライアントの send() にコマンドを渡して呼び出します。.promise() は不要です。
- 2026年9月時点のv3はNode.js 20以上が必須です。v3によるNode.js 20のサポートは2027年1月に終了する予定です。Node.js 20自体は2026年4月30日にEOLを迎えています。
- v2からの移行は aws-sdk-js-codemod で大半を機械変換できますが、S3 GetObject の Body.toString() は変換されず、実行すると [object Object] が返ります。
- v3.729.0(2025年1月15日公開)以降、S3へのアップロードにCRC32チェックサムが既定で付きます。非対応のS3互換ストレージでは requestChecksumCalculation を WHEN_REQUIRED にします。
- DynamoDBの DocumentClient は、ネストしたオブジェクトや配列の中に undefined があるとエラーになります。removeUndefinedValues: true で回避します。
AWS SDK for JavaScript v3の概要とv2との違い
v3は2020年12月にv3.0.0がnpmへ公開された、v2の全面的な書き直しです。サービス単位のパッケージ分割、コマンドオブジェクトによる呼び出し、ミドルウェアスタックによる拡張の3点が、v2のコードとの見た目の差として現れます。SDKそのものの位置づけ(APIやライブラリとの違い)はSDKとは?API・ライブラリとの違いを比較で整理しています。
パッケージ構成と呼び出し方の比較
| 項目 | v2 | v3 |
|---|---|---|
| パッケージ | aws-sdk(全サービス同梱) | @aws-sdk/client-s3 など個別 |
| 呼び出し | s3.getObject(params).promise() | client.send(new GetObjectCommand(params)) |
| 全体設定 | AWS.config.update() | なし(クライアントごとに指定) |
| S3の本文 | Buffer | ストリーム(transformToString等で読む) |
| 拡張点 | イベントリスナー | ミドルウェアスタック |
| TypeScript型 | 同梱 | 各クライアントに同梱 |
v3にもv2風の書き方ができる集約クライアント(new S3() と s3.getObject())が残っていますが、公式が基本形として示すのは S3Client と個別コマンドの組み合わせです。ブラウザ向けでは後述のとおりバンドルサイズに差が出ます。
v2のサポート終了日とv3が要求するNode.jsの版
v2は2024年9月8日にメンテナンスモードへ入り、重大な不具合とセキュリティの修正だけを受ける期間を経て、2025年9月8日にサポートが終了しました。AWSの告知では、既存アプリケーションはAWS側のサービスに根本的な変更がない限り動き続けるとしていますが、新しいサービスや新機能には追随しません。
v3はNode.jsのリリーススケジュールに合わせてサポート対象を決めています。AWSの方針は、全LTS版でテストし、直近にEOLを迎えた版だけ追加で8か月サポートするというものです(AWS Developer Tools Blogの告知)。2027年以降の日付は、この告知の表でもNode.jsのリリーススケジュールに基づく見込みとされています。
| Node.js | Node.js自体のEOL | v3のサポート終了 |
|---|---|---|
| 18.x | 2025-04-30 | 2026-01-13の3.968.0で終了 |
| 20.x | 2026-04-30 | 2027年1月(予定) |
| 22.x | 2027-04-30 | 2028年1月(予定) |
| 24.x | 2028-04-30 | 2029年1月(予定) |
npmの @aws-sdk/client-s3 3.1132.0 は package.json の engines に node >=20.0.0 を宣言しています。Node.js 20でビルドしているCIは、本体がすでにEOLを迎えているため、SDK側の対応終了を待たず、サポート中のNode.js 22または24へ移行します。Node.jsの版の選び方はNode.jsとは?仕組み・版の選び方・標準機能で動かす手順を参照してください。
v3のインストールと最初のAPI呼び出し
インストールとS3Clientの生成
使うサービスのクライアントだけをインストールします。S3なら次の1パッケージです。
npm install @aws-sdk/client-s3
バケット内のオブジェクトを最大10件表示するコードです。index.mjsとして保存し、Bucketを実在するバケット名へ変更します。対象バケットへのs3:ListBucket権限を持つ認証情報を設定し、node index.mjsで実行します。
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "ap-northeast-1" });
const res = await s3.send(
new ListObjectsV2Command({ Bucket: "my-bucket", MaxKeys: 10 })
);
for (const obj of res.Contents ?? []) {
console.log(obj.Key, obj.Size);
}
クライアントはリクエストごとに作らず、モジュールの先頭で1つ作って使い回します。v3のクライアントは既定で最大3回の試行(maxAttempts: 3、retryMode: standard)を行うので、単発の通信エラーで自前のリトライを重ねる必要はありません。実測でも client.config.maxAttempts() は 3、retryMode() は standard を返しました。
認証情報の読み込み順と「Could not load credentials」エラー
credentials を指定しないと、Node.jsでは @aws-sdk/credential-provider-node の既定チェーンが上から順に認証情報を探します。3.1132.0 のソースで確認した順序は次のとおりです。
- 環境変数(AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY)
- SSO(クライアント生成時にSSOの項目を明示した場合のみ)
- 共有設定ファイル(~/.aws/credentials と ~/.aws/config。SSOプロファイルもここで読む)
- credential_process で指定した外部コマンド
- Web Identityトークンファイル(EKSのIRSAなど)
- コンテナ認証情報用の環境変数がある場合はコンテナのエンドポイント、ない場合はEC2インスタンスメタデータ(無効化されていない場合)
@aws-sdk/credential-providers のAPIリファレンスは、Web Identityトークンを共有設定ファイルより先に書いていますが、実装では共有設定ファイルと credential_process が先に評価されます。~/.aws/config とトークンファイルの両方がある環境では、実装の順序で考えてください。
見落としやすいのは、環境変数 AWS_PROFILE が設定されていると、アクセスキーの環境変数があっても1番目が飛ばされる点です。両方を設定した場合はプロファイル側が使われ、Multiple credential sources detected という警告が出ます。CIで一時的にキーを環境変数に入れても別アカウントのプロファイルで動いてしまう、という事故はこれが原因です。
どこにも認証情報がないと、send() は CredentialsProviderError を投げ、メッセージは Could not load credentials from any providers になります。HOMEを空にした環境で実行して、このメッセージを確認しました。ローカル開発ではアクセスキーを置かず、aws loginコマンドの実行手順とsso login・アクセスキーとの使い分けで説明しているSSOのプロファイルを使うのが安全です。AWS CLIの設定ファイルの作り方はAWS CLIの導入と運用にまとめています。
S3とDynamoDBの基本操作
S3 GetObjectの本文取得とtransformToStringによる文字列化
v3の GetObject が返す Body はストリームです。文字列として読むには transformToString()、バイト列なら transformToByteArray() を使います。
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "ap-northeast-1" });
const res = await s3.send(
new GetObjectCommand({ Bucket: "cfg", Key: "config.json" })
);
const config = JSON.parse(await res.Body.transformToString());
v2の感覚で res.Body.toString() と書くと、エラーにならず [object Object] という文字列が返ります。JSON.parse の段階で初めて失敗するため、原因がSDKの移行にあると気づきにくい箇所です。S3の権限設計や署名付きURLはAWS S3の使い方で扱っています。
大きなファイルのアップロードは@aws-sdk/lib-storageのUpload
実行前にnpm install @aws-sdk/lib-storageで追加パッケージを入れ、読み込むdb.dumpを用意します。
PutObjectCommand は1リクエストで送るため、数百MBを超えるファイルやサイズが事前に分からないストリームには向きません。@aws-sdk/lib-storage の Upload はマルチパートアップロードを自動で組み立てます。
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";
const upload = new Upload({
client: new S3Client({ region: "ap-northeast-1" }),
params: {
Bucket: "my-bucket",
Key: "backup/db.dump",
Body: createReadStream("./db.dump"),
},
queueSize: 4,
partSize: 8 * 1024 * 1024,
});
upload.on("httpUploadProgress", (p) => console.log(p.loaded, p.total));
await upload.done();
lib-storage のソースでは並列数 queueSize の既定値が4、パートの最小サイズが5MB(1024 * 1024 * 5)です。帯域が細い環境では queueSize を下げ、パートサイズを上げてリクエスト数を減らします。
DynamoDBはlib-dynamodbとremoveUndefinedValues
実行前にnpm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbで依存を追加します。usersテーブルには文字列型のパーティションキーpkを用意し、実行する認証情報にdynamodb:PutItemとdynamodb:GetItemを許可します。
DynamoDBの低レベルAPIは { S: "taro" } のような型付きの形式を要求します。@aws-sdk/lib-dynamodb の DynamoDBDocumentClient を挟むと、普通のJavaScriptオブジェクトのまま読み書きできます。
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
} from "@aws-sdk/lib-dynamodb";
const doc = DynamoDBDocumentClient.from(
new DynamoDBClient({ region: "ap-northeast-1" }),
{ marshallOptions: { removeUndefinedValues: true } }
);
await doc.send(new PutCommand({
TableName: "users",
Item: { pk: "user#1", profile: { nickname: undefined }, tags: ["a"] },
}));
const { Item } = await doc.send(
new GetCommand({ TableName: "users", Key: { pk: "user#1" } })
);
removeUndefinedValues を付けない場合の挙動を、通信をしないダミーのハンドラで確かめた結果です。
| Itemの形 | 既定 | removeUndefinedValues: true |
|---|---|---|
| { pk, nickname: undefined } | 成功(属性を省略) | 成功(属性を省略) |
| { pk, profile: { nickname: undefined } } | エラー | 成功(profileは空のM) |
{ pk, tags: ["a", undefined] } |
エラー | 成功(tagsは["a"]) |
エラー時のメッセージは Pass options.removeUndefinedValues=true to remove undefined values from map/array/set. です。トップレベルの undefined は通るため、単体テストで平らなオブジェクトしか使っていないと、本番でネストした入力が来て初めて落ちます。フォームの任意項目をそのまま保存するコードでは最初から有効にしておきます。テーブル設計側の考え方はDynamoDBとは:ローカル起動からキー設計・容量モード選定までの実装手順を参照してください。
一覧取得のページングはpaginate関数
ListObjectsV2 は1回で最大1,000件しか返しません。v3には各サービスに paginate で始まる関数があり、for await で全ページを回せます。
import { S3Client, paginateListObjectsV2 } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "ap-northeast-1" });
let total = 0;
for await (const page of paginateListObjectsV2(
{ client: s3 },
{ Bucket: "my-bucket", Prefix: "logs/" }
)) {
total += page.KeyCount ?? 0;
}
console.log(total);
作成直後のリソースを待つ処理には waitUntil で始まる関数(S3なら waitUntilObjectExists)が用意されています。v2の waitFor() の置き換えです。
v2からv3への移行手順
aws-sdk-js-codemodで機械変換できる範囲
AWSが公開している aws-sdk-js-codemod は、v2のコードをv3の書き方へ書き換えるツールです。次のv2のコードを 3.0.2 で変換しました。
const AWS = require("aws-sdk");
const s3 = new AWS.S3({ region: "ap-northeast-1" });
async function main() {
const obj = await s3.getObject({ Bucket: "cfg", Key: "config.json" }).promise();
console.log(obj.Body.toString());
const ddb = new AWS.DynamoDB.DocumentClient();
await ddb.put({ TableName: "users", Item: { pk: "u#1" } }).promise();
}
main();
npx [email protected] -t v2-to-v3 app.js
変換後のコードは次のようになりました。
const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb");
const { DynamoDB } = require("@aws-sdk/client-dynamodb");
const { S3 } = require("@aws-sdk/client-s3");
const s3 = new S3({
region: "ap-northeast-1"
});
async function main() {
const obj = await s3.getObject({ Bucket: "cfg", Key: "config.json" });
console.log(obj.Body.toString());
const ddb = DynamoDBDocument.from(new DynamoDB());
await ddb.put({ TableName: "users", Item: { pk: "u#1" } });
}
main();
require の差し替え、.promise() の削除、DocumentClient から DynamoDBDocument への置き換えまでは自動で済みました。一方で、生成されるのはv2風の集約クライアント(S3、DynamoDB)で、S3Client と個別コマンドの形にはなりません。
変換後に手で直す3か所
codemodの出力をそのまま動かすと、次の3か所で問題が残ります。
- obj.Body.toString():変換されずに残ります。変換後のコードを実行すると [object Object] が出力されました。await obj.Body.transformToString() に書き換えます。
- AWS.config.update() による全体設定:v3には全体設定がありません。リージョンやリトライ回数は、各クライアントのコンストラクタに渡すか、共通の設定オブジェクトを作って各クライアントへ展開します。
- ブラウザ向けの集約クライアント:Node.jsのサーバーでは集約クライアントのままで困りませんが、ブラウザへ配るコードは S3Client と必要なコマンドだけのimportに直すとバンドルが小さくなります(次章で実測)。
v2とv3はパッケージ名が異なるため、同じプロジェクトに共存できます。今回の検証でも aws-sdk 2.1693.0 と @aws-sdk/client-s3 3.1132.0 を同じ node_modules に入れて動かしました。モジュール単位でv3に置き換え、テストが通るたびにv2の呼び出しを減らしていく進め方が取れます。
S3互換ストレージでアップロードが失敗するチェックサム既定値
v3.729.0(2025年1月15日にnpmへ公開)から、S3クライアントは既定でアップロードにチェックサムを付け、ダウンロード時に検証するようになりました。3.1132.0 で PutObjectCommand が送るリクエストヘッダーを確かめると、x-amz-sdk-checksum-algorithm と x-amz-checksum-crc32 の2つが付いていました。
AWSのS3では問題になりませんが、この新しいヘッダーに対応していないS3互換ストレージではアップロードが失敗します。Cloudflare R2のコミュニティでは、3.729.0 から PutObject と UploadPart が 501 NotImplemented で失敗するという報告が上がりました。MinIOなどでも同種の報告があります。SDKの版を上げただけでアップロードが失敗し始めたら、まずこの変更を疑います。
対処は、チェックサムを必要なAPIでだけ付ける WHEN_REQUIRED への切り替えです。
const s3 = new S3Client({
region: "auto",
endpoint: "https://ACCOUNT_ID.r2.cloudflarestorage.com",
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
});
コードを変えられない場合は、環境変数 AWS_REQUEST_CHECKSUM_CALCULATION=when_required でも送信側を変更できます。上のコードと同様に受信側も変更するには、AWS_RESPONSE_CHECKSUM_VALIDATION=when_required を併せて設定します。コンストラクタ指定と環境変数のそれぞれで、チェックサムのヘッダーが消えることを確認しました。AWSのS3だけを使うなら既定値のままにしておくべきで、この設定を一律に入れると転送中の破損を検出する仕組みを自分で外すことになります。
ミドルウェアスタックの構造と追加方法
v3のクライアントは、1回のAPI呼び出しを initialize、serialize、build、finalizeRequest、deserialize の5つのステップに分けたミドルウェアの列で処理します。S3Client の PutObjectCommand について middlewareStack.identify() で並びを出力すると、次の順でした(抜粋)。
| ステップ | 主なミドルウェア | 役割 |
|---|---|---|
| initialize | validateBucketNameMiddleware | 入力の検証 |
| serialize | serializerMiddleware | 入力をHTTPリクエストへ変換 |
| build | contentLengthMiddleware ほか | ヘッダーの付与 |
| finalizeRequest | retryMiddleware、httpSigningMiddleware | リトライと署名 |
| deserialize | deserializerMiddleware | レスポンスを出力へ変換 |
置くステップによって、リトライ時に実行される回数が変わります。常に503を返すダミーのハンドラでHeadBucketを呼び、3回試行させたときの実行回数は、initialize が1回、build が1回、finalizeRequest が3回でした。リトライ処理が finalizeRequest の中で次のミドルウェアを繰り返し呼ぶためです。priority を指定せず add() した場合、試行ごとのログを取りたいなら finalizeRequest、所要時間をリトライ込みで測りたいなら initialize に置きます。priority や addRelativeTo で順序を変えると回数も変わるので、その場合は同じ方法で実行回数を確かめてください。
s3.middlewareStack.add(
(next, context) => async (args) => {
const start = Date.now();
try {
return await next(args);
} finally {
console.log(context.commandName, Date.now() - start, "ms");
}
},
{ step: "initialize", name: "timingMiddleware" }
);
name を付けておくと middlewareStack.remove("timingMiddleware") で外せます。リクエストヘッダーを書き換えたい場合は build に置き、args.request.headers を編集します。リトライの間隔やジッターの考え方はリトライパターンとは?指数バックオフ・ジッター・リトライ予算の実装判断で解説しています。
LambdaとブラウザでSDKを使うときの注意点
Lambda同梱SDKのバージョン確認と依存関係の固定
LambdaのNode.jsランタイム(nodejs22.x、nodejs24.x、nodejs26.x)にはv3が同梱されており、依存に含めなくても import できます。ただしAWSのドキュメントによると、同梱されるのはv3の特定のマイナー版で、最新版ではなく、版はランタイムとリージョンによって異なります。v2はどのランタイムにも含まれません。
同梱版を確認するには、次のハンドラを実行します(Lambdaのドキュメントに掲載されている方法です)。
import packageJson from "@aws-sdk/client-s3/package.json" with { type: "json" };
export const handler = async () => ({ version: packageJson.version });
ローカルで使った版より古いSDKが本番で動くと、新しいパラメーターが無視されたり、前述のチェックサム既定値のように挙動が変わったりします。新しい機能に依存するコードや、版を固定したい本番環境では、SDKを依存に入れてバンドルします。Lambdaの仕組みや料金はAWS Lambdaとは?仕組み・料金体系とコールドスタート対策・採用判断を参照してください。
ブラウザ向けのバンドルサイズ実測
esbuild 0.28.2 で、ブラウザ向けに minify したESMバンドルのサイズを測りました。各ファイルはクライアント(とコマンド)をimportし、console.logで参照する最小構成です。esbuildには--bundle --minify --platform=browser --format=esmを指定し、生成ファイルのバイト数とgzip -cの出力バイト数を測りました。
| import内容 | minify後 | gzip後 |
|---|---|---|
| v3 S3Client+PutObjectCommand | 261,657 B | 82,362 B |
| v3 集約クライアント S3 | 281,443 B | 86,862 B |
| v2 aws-sdk/clients/s3 | 427,681 B | 106,699 B |
| v2 aws-sdk(全体) | 3,392,359 B | 470,048 B |
| v3 DynamoDB+DocumentClient | 178,405 B | 58,009 B |
| v2 aws-sdk/clients/dynamodb | 337,090 B | 95,433 B |
v2で aws-sdk 全体を読み込んでいたコードなら、v3への移行でgzip後のサイズは約6分の1になります。v2でもサービス単位でimportしていた場合の差はS3で約23%にとどまり、S3クライアントだけで80KB(gzip後)を超えます。ブラウザから大きなファイルを扱う用途なら、SDKを配らずにサーバー側で署名付きURLを発行し、ブラウザは fetch で直接アップロードする構成のほうが軽くなります。バンドラーの設定はesbuildとは?Go製の高速バンドラの使い方と採用判断を参照してください。
ブラウザでSDKを使う場合、アクセスキーをコードに埋め込んではいけません。@aws-sdk/credential-providers の fromCognitoIdentityPool でCognitoのIDプールから一時的な認証情報を受け取り、S3バケット側にはCORSの設定を入れます。Cognito側の構築はCognitoで認証基盤を実装する手順で説明しています。
aws-sdk-client-mockによる単体テスト
SDKを呼ぶ関数のテストでは、AWSへの通信をさせずに応答を差し替えます。コミュニティ製の aws-sdk-client-mock を使うと、クライアントとコマンドの組み合わせごとに返り値を指定できます。Node.js標準のテストランナーで実行し、2件とも成功したコードです。実行前にnpm install -D aws-sdk-client-mock @smithy/util-streamで開発依存を追加し、mock.test.mjsとして保存してnode --test mock.test.mjsで実行します。
import { test } from "node:test";
import assert from "node:assert/strict";
import { Readable } from "node:stream";
import { mockClient } from "aws-sdk-client-mock";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { sdkStreamMixin } from "@smithy/util-stream";
async function loadConfig(s3, bucket) {
const res = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: "config.json" }));
return JSON.parse(await res.Body.transformToString());
}
const s3Mock = mockClient(S3Client);
test("S3のGetObjectをモックして設定を読む", async () => {
s3Mock.reset();
const body = sdkStreamMixin(Readable.from([Buffer.from('{"feature":true}')]));
s3Mock.on(GetObjectCommand, { Bucket: "cfg", Key: "config.json" }).resolves({ Body: body });
const conf = await loadConfig(new S3Client({ region: "ap-northeast-1" }), "cfg");
assert.deepEqual(conf, { feature: true });
assert.equal(s3Mock.commandCalls(GetObjectCommand).length, 1);
});
test("NoSuchKeyを投げさせる", async () => {
s3Mock.reset();
const err = Object.assign(new Error("not found"), { name: "NoSuchKey" });
s3Mock.on(GetObjectCommand).rejects(err);
await assert.rejects(loadConfig(new S3Client({ region: "ap-northeast-1" }), "cfg"), { name: "NoSuchKey" });
});
GetObject の Body は transformToString() を持つストリームである必要があるため、sdkStreamMixin で包みます。この @smithy/util-stream はSDKの依存として node_modules の奥に入っているだけで、直接importすると ERR_MODULE_NOT_FOUND になりました。npm install -D @smithy/util-stream で明示的に入れてください。mockClient はクライアントのプロトタイプを差し替えるので、テスト対象の関数の中で new S3Client() していてもモックが効きます。モックとスタブの使い分けはモックとは?スタブとの違い・テストダブル5分類と単体テストでの使い分けを参照してください。
よくある質問
AWS SDK for JavaScript v2のサポートはいつ終了しましたか?
2025年9月8日です。2024年9月8日からの1年間はメンテナンスモードで、重大な不具合とセキュリティの修正だけが提供されていました。終了後も既存のコードは動きますが、新しいAWSサービスや機能には対応しないため、v3へ移行します。
v3の最新バージョンはどう確認しますか?
npm view @aws-sdk/client-s3 version で確認できます。2026年9月15日時点の最新は 3.1132.0 です。v3は頻繁に版が上がり、npm view @aws-sdk/client-s3 time で公開履歴を数えると、2026年8月(UTC)だけで22の版が公開されていました。全クライアントの版番号が揃えて上がるので、複数のクライアントを使う場合は同じ版に揃えます。
TypeScriptで使う場合、型定義パッケージを別に入れる必要はありますか?
必要ありません。@aws-sdk/client-s3 などの各クライアントは package.json の types に型定義(dist-types/index.d.ts)を指定しており、インストールしただけで型が付きます。@aws-sdk/types はクライアントの依存として入る共通の型のパッケージで、ミドルウェアなどを自作するときに直接importする程度です。
v2とv3を同じプロジェクトで併用できますか?
できます。v2は aws-sdk、v3は @aws-sdk/ で始まる別名のパッケージなので衝突しません。モジュール単位で段階的に置き換え、最後に aws-sdk を依存から外します。ただし、v2の AWS.config で設定したリージョンや認証情報はv3のクライアントには反映されないため、移行したクライアントには設定を個別に渡します。
ブラウザから直接S3にアクセスするには何が必要ですか?
一時的な認証情報とバケットのCORS設定の2つです。認証情報は @aws-sdk/credential-providers の fromCognitoIdentityPool でCognitoのIDプールから取得し、IAMロールで操作できるバケットとプレフィックスを絞ります。CORS設定がないと、ブラウザがプリフライトの段階でリクエストを止めます。アップロードだけならSDKをブラウザに配らず、署名付きURLを使う方法もあります。