You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SvelteKit的page.server.ts中通过Node.js SDK上传文件到Cloudinary

在SvelteKit后端通过Cloudinary Node.js SDK上传文件(非Base64/前端直传)

前置准备

  1. 确保已安装Cloudinary SDK:npm install cloudinary
  2. 在SvelteKit项目根目录创建.env文件,填入Cloudinary凭证:
    CLOUDINARY_CLOUD_NAME=你的云名称
    CLOUDINARY_API_KEY=你的API密钥
    CLOUDINARY_API_SECRET=你的API密钥密码
    

前端表单修正(page.svelte)

原表单存在标签未闭合、用户无法触发文件选择的问题,调整后代码如下:

<form
    method="post"
    enctype="multipart/form-data"
    use:enhance
    action="?/upload"
>
    <input
        class="hidden"
        name="image"
        id="file-to-upload"
        type="file"
        accept=".png,.jpg"
        on:change={() => this.form.submit()}
    />

    <button 
        class="border bg-orange-400 rounded py-2 text-sm"
        type="button"
        onclick={() => document.getElementById('file-to-upload')?.click()}
    >
        Upload!
    </button>
</form>
  • 点击按钮会触发隐藏的文件选择框
  • 选择文件后自动提交表单,配合use:enhance实现无刷新提交

后端上传逻辑实现(+page.server.ts)

核心是将FormData中的文件流转换为Node.js兼容流,通过Cloudinary的upload_stream完成上传,同时用Promise包装回调逻辑适配async/await:

import { v2 as cloudinary, type UploadApiResponse } from 'cloudinary';
import { Readable } from 'stream/web';
import { CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } from '$env/static/private';
import type { Actions } from './$types';

// 初始化Cloudinary
cloudinary.config({
    cloud_name: CLOUDINARY_CLOUD_NAME,
    api_key: CLOUDINARY_API_KEY,
    api_secret: CLOUDINARY_API_SECRET
});

export const actions = {
    upload: async ({ request }) => {
        const formData = await request.formData();
        const file = formData.get('image') as File | null;

        if (!file) {
            return { success: false, error: '未选择文件' };
        }

        // 将Web标准ReadableStream转换为Node.js可读流
        const nodeReadableStream = Readable.fromWeb(file.stream());

        // 用Promise包装upload_stream的回调逻辑
        const uploadPromise = new Promise<UploadApiResponse>((resolve, reject) => {
            const uploadStream = cloudinary.uploader.upload_stream(
                {
                    // 可选配置:自定义上传文件夹、文件格式等
                    folder: 'sveltekit-uploads',
                    format: file.type.split('/')[1]
                },
                (error, result) => {
                    if (error) {
                        reject(error);
                    } else if (result) {
                        resolve(result);
                    } else {
                        reject(new Error('上传失败,无返回结果'));
                    }
                }
            );

            // 将文件流管道到Cloudinary上传流
            nodeReadableStream.pipe(uploadStream);
        });

        try {
            const uploadResult = await uploadPromise;
            return { success: true, data: { url: uploadResult.secure_url, publicId: uploadResult.public_id } };
        } catch (error) {
            console.error('上传错误:', error);
            return { success: false, error: (error as Error).message };
        }
    }
} satisfies Actions;

关键逻辑说明

  1. 流转换:SvelteKit服务端接收的File对象返回的是Web标准ReadableStream,通过Readable.fromWeb()转换为Node.js兼容的可读流,才能被Cloudinary的upload_stream消费。
  2. Promise包装:Cloudinary的upload_stream基于回调实现,包装为Promise后可以用async/await优雅处理异步结果。
  3. 错误处理:捕获上传过程中的错误,返回结构化结果给前端,方便页面展示提示。

内容的提问来源于stack exchange,提问作者Alexander Guskov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 17:05:22