Skip to content

Instantly share code, notes, and snippets.

@MehmetAdemi
Last active August 13, 2025 08:29
Show Gist options
  • Select an option

  • Save MehmetAdemi/a1c83d97fcaf2c773c2913dbd2471de0 to your computer and use it in GitHub Desktop.

Select an option

Save MehmetAdemi/a1c83d97fcaf2c773c2913dbd2471de0 to your computer and use it in GitHub Desktop.
Next.js App Router - Backend caption route in the Remotion Editor Starter (https://remotion.dev/docs/editor-starter/backend-routes)
// src/app/api/upload/route.ts
// File upload handling
import {getSignedUrl} from '@aws-sdk/s3-request-presigner';
import {AwsRegion} from '@remotion/lambda';
import {getAwsClient} from '@remotion/lambda/client';
import {NextRequest, NextResponse} from 'next/server';
import {
PresignErrorCode,
PresignErrorResponse,
PresignResponse,
} from '@/editor/assets/types';
import {formatBytes} from '@/editor/utils/format-bytes';
import {requireServerEnv} from '@/editor/utils/server-env';
interface GetFileUrlParams {
key: string;
bucketName: string;
region: AwsRegion;
transferAcceleration: boolean;
}
export const getEndPoint = ({
bucketName,
region,
transferAcceleration,
}: {
bucketName: string;
region: AwsRegion;
transferAcceleration: boolean;
}) => {
if (transferAcceleration) {
return `https://${bucketName}.s3-accelerate.amazonaws.com`;
}
return `https://${bucketName}.s3.${region}.amazonaws.com`;
};
export const getReadUrl = ({
key,
bucketName,
region,
transferAcceleration,
}: GetFileUrlParams) => {
return `${getEndPoint({bucketName, region, transferAcceleration})}/${key}`;
};
export interface UploadResponse {
presignedUrl: string;
readUrl: string;
fileKey: string;
}
export interface UploadErrorResponse {
code: PresignErrorCode;
message: string;
}
class UploadError extends Error {
code: PresignErrorCode;
constructor(code: PresignErrorCode, message: string) {
super(message);
this.name = 'UploadError';
this.code = code;
}
}
const generatePresignedUrl = async ({
contentType,
contentLength,
expiresIn,
bucketName,
region,
}: {
contentType: string;
contentLength: number;
expiresIn: number;
bucketName: string;
region: AwsRegion;
}): Promise<PresignResponse> => {
const {REMOTION_AWS_TRANSFER_ACCELERATION} = requireServerEnv();
const {client, sdk} = getAwsClient({
region,
service: 's3',
});
const fileKey = crypto.randomUUID();
const command = new sdk.PutObjectCommand({
Bucket: bucketName,
Key: fileKey,
ACL: 'public-read',
ContentLength: contentLength,
ContentType: contentType,
});
const presignedUrl = await getSignedUrl(client, command, {
expiresIn,
});
const transferAcceleration =
REMOTION_AWS_TRANSFER_ACCELERATION === 'true' ||
REMOTION_AWS_TRANSFER_ACCELERATION === '1';
// The location of the asset after the upload
const readUrl = getReadUrl({
key: fileKey,
bucketName,
region,
transferAcceleration,
});
return {presignedUrl, readUrl, fileKey};
};
const MAX_FILE_UPLOAD_SIZE_IN_MB = 1000;
export async function POST(request: NextRequest) {
try {
const serverEnv = requireServerEnv();
const json = await request.json();
if (!Number.isFinite(json.size)) {
throw new Error('size is not a number');
}
if (typeof json.contentType !== 'string') {
throw new Error('contentType is not a string');
}
if (json.size > 1024 * 1024 * MAX_FILE_UPLOAD_SIZE_IN_MB) {
return NextResponse.json(
{
code: 'FILE_TOO_LARGE',
message: `File may not be over ${MAX_FILE_UPLOAD_SIZE_IN_MB}MB. Yours is ${formatBytes(json.size)}.`,
},
{status: 413},
);
}
const response = await generatePresignedUrl({
contentType: json.contentType,
contentLength: json.size,
expiresIn: 60 * 60 * 24 * 7,
bucketName: serverEnv.REMOTION_AWS_BUCKET_NAME,
region: serverEnv.REMOTION_AWS_REGION,
});
return NextResponse.json(response);
} catch (error) {
if (error instanceof UploadError) {
return NextResponse.json(
{
code: error.code,
message: error.message,
},
{status: 400},
);
}
const errorResponse: PresignErrorResponse = {
code: 'UNKNOWN_ERROR',
message: 'Upload service unavailable',
};
// eslint-disable-next-line no-console
console.error('Upload API error:', error);
return NextResponse.json(errorResponse, {status: 500});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment