Загрузка и показ изображений в Supabase из Next.js

К чему приведёт это руководство
- Научитесь создавать проект Supabase и storage bucket.
- Настроите supabase-js в Next.js и environment-переменные.
- Реализуете форму загрузки файла и загрузку в bucket с уникальным именем.
- Получите публичный URL и выведете изображение в компоненте Image Next.js.
- Узнаете, когда такой подход не подойдёт, и получите рекомендации по безопасности и соответствию требованиям конфиденциальности.
Important: примеры кода — на JavaScript/React. Поддерживаются Next.js 12+ и App Router в Next.js 13+, но там есть отличия в структуре страниц — в тексте указаны альтернативы.
Краткое определение терминов
- Supabase Storage: объектное хранилище, похожее на S3, интегрированное с Supabase.
- Bucket: контейнер для файлов внутри Supabase Storage.
- anon key: публичный ключ клиента для браузера (ограниченные права).
Создание проекта Supabase
Если у вас ещё нет приложения Next.js, сначала создайте минимальный проект по официальному руководству Next.js.
Далее — создание проекта Supabase:
- Перейдите на сайт Supabase и зарегистрируйтесь или войдите в аккаунт.
- В панели управления нажмите Create a new project.
- Укажите имя проекта и нажмите Create project.
- После создания вы попадёте в дашборд проекта.
После создания проекта создайте storage bucket (раздел ниже). Сохраняйте URL проекта и anon key в .env-файле.
Создание хранилища (bucket) в Supabase
Bucket — это место, где хранятся ваши медиафайлы (изображения, видео). Bucket можно создать через дашборд или программно через клиент.
Через дашборд:
- В дашборде проекта откройте раздел Storage.
- Нажмите New Bucket.
- Укажите имя, например images, и при необходимости настройте публичный доступ.
- Нажмите Create Bucket.
Важно: если вы хотите, чтобы файлы были доступны публично через прямой URL без авторизации, отметьте bucket как публичный или создайте публичные политики доступа. В противном случае выдавайте временные URL (signed URL) для доступа.
Настройка клиента Supabase в приложении
Установите библиотеку supabase-js:
npm install @supabase/supabase-jsСоздайте папку lib в корне проекта или внутри src и файл supabase.js. В примерах ниже используется Node-совместимая и браузерная инициализация через createClient.
.env.local (в корне проекта Next.js)
SUPABASE_PROJECT_URL="your_project_url"
SUPABASE_ANON_KEY="your_project_anon_key"lib/supabase.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.SUPABASE_PROJECT_URL,
process.env.SUPABASE_ANON_KEY
)Совет: в Vercel и других платформах задайте эти переменные в настройках окружения, а не закладывайте ключи в репозиторий.
Создание страницы с формой для загрузки
Если вы используете App Router (Next.js 13+), создайте папку app/upload и файл page.js. Для Next.js 12 создайте pages/upload.js.
app/upload/page.js (пример для App Router):
"use client"
import { useState } from "react"
import { v4 as uuidv4 } from "uuid"
import { supabase } from "@/lib/supabase"
export default function Page() {
const [file, setFile] = useState(null)
const [uploading, setUploading] = useState(false)
const [error, setError] = useState(null)
const [publicUrl, setPublicUrl] = useState(null)
const handleFileSelected = (e) => {
setFile(e.target.files[0])
}
const handleSubmit = async (e) => {
e.preventDefault()
if (!file) return
setUploading(true)
setError(null)
try {
const filename = `${uuidv4()}-${file.name}`
const { data, error: uploadError } = await supabase.storage
.from('images')
.upload(filename, file, {
cacheControl: '3600',
upsert: false,
})
if (uploadError) throw uploadError
const filepath = data.path
// Получение публичного URL
const { data: urlData } = supabase.storage
.from('images')
.getPublicUrl(filepath)
setPublicUrl(urlData.publicUrl)
} catch (err) {
setError(err.message)
} finally {
setUploading(false)
}
}
return (
)
}Замечания:
- Установите пакет uuid: npm install uuid
- В браузере безопаснее использовать public anon key, но не используйте service_role ключ в клиентском коде.
Загрузка файла в Supabase Storage
Ключевые моменты при загрузке:
- Создавайте уникные имена файлов (UUID + оригинальное имя) — чтобы избежать перезаписей.
- Используйте опцию cacheControl для кеширования в CDN.
- Управляйте upsert: false чтобы не затирать существующие файлы.
Пример загрузки (внутри handleSubmit) уже показан выше. После upload Supabase вернёт объект data с полем path.
Получение URL и вывод изображения
Два способа получить URL:
- Через метод getPublicUrl клиента Supabase:
const { data } = supabase
.storage
.from('images')
.getPublicUrl(filepath)
// data.publicUrl — публичный URL- Сконструировать URL вручную: соединить URL вашего проекта, имя bucket и путь — но лучше использовать метод клиента, он учтёт путь и префиксы.
В Next.js можно выводить изображение через компонент Image, но добавьте домен в next.config.js (если используете Image):
next.config.js
module.exports = {
images: {
domains: ['your-project-ref.supabase.co'],
},
}Или используйте обычный тег img как в примере выше.
Политики доступа и разрешения
Чтобы приложение могло загружать и читать файлы, настройте политику доступа:
- В дашборде Supabase откройте Storage > ваш bucket > Access.
- Нажмите New policy.
- Выберите For full customization и создайте политику, которая даёт INSERT и SELECT (если вам нужно только чтение — создайте политику только для SELECT).
- Проверьте роли и убедитесь, что политики не открывают конфиденциальные файлы посторонним.
Важно: вместо открытого bucket для приватных файлов используйте signed URL (временный доступ), а для публичного контента — публичный bucket.
Когда такой подход не подойдёт (counterexamples)
- Если вам требуется транзакционная целостность между записью большого BLOB и другими операциями БД (труднее атомарно откатить и файл, и данные).
- Если файлы очень большие и требуются chunked-upload/streaming. Supabase Storage подходит не для всех случаев стриминга больших мультимедийных файлов.
- Когда строгие требования по хранению и шифрованию на стороне сервера не позволяют использовать публичные облачные buckets.
Альтернативные подходы
- Хранить файлы в PostgreSQL (bytea) — подходит для очень небольших файлов, но обычно дороже по производительности.
- Использовать S3/MinIO/Backblaze B2 напрямую, если уже есть готовая инфраструктура.
- CDN + Signed URLs: для приватного контента создавайте временные ссылки и кешируйте через CDN.
Пошаговая методология внедрения (mini-methodology)
- Оцените требования: публичный/приватный контент, размер файлов, частота запросов.
- Создайте проект Supabase и bucket.
- Настройте env-переменные и клиента.
- Реализуйте форму загрузки и тестовую страницу.
- Настройте политики доступа и протестируйте операции INSERT/SELECT.
- Добавьте мониторинг/логирование и автоматические тесты.
Чек-лист для ролей
Разработчик:
- Настроил supabase.js и environment-переменные.
- Реализовал загрузку и получение public URL.
- Обработал ошибки и показывается прогресс загрузки.
DevOps:
- Проверил переменные окружения на staging/production.
- Настроил domains в next.config.js (если используете Image).
- Настроил бэкап и политику хранения.
Безопасность:
- Убедился, что service_role ключ не в клиентском коде.
- Настроил политики доступа для bucket.
- При необходимости внедрил signed URLs и их срок действия.
Критерии приёмки
- Форма загружает файл и возвращает public URL.
- Изображение корректно отображается на странице.
- В логах нет ошибок загрузки при типичных сценариях.
- Политики доступа соответствуют требованиям безопасности.
Тестовые сценарии и приёмка
- Успешная загрузка изображения (jpeg/png), отобразить URL.
- Попытка загрузки без файла — блокировка отправки.
- Ошибка сети — обработка и показ сообщения.
- Попытка перезаписи существующего файла — проверка upsert:false.
- Доступ к приватному файлу без подписанного URL — отказ.
Rollback / инцидентный план
- Если файлы стали публичными по ошибке: немедленно снять публичный доступ у bucket, регенерировать политики и при необходимости перевыпустить ссылки для пользователей.
- Если файлы были перезаписаны: восстановить из бэкапа (если настроен бэкап). Рекомендуется хранить запись в БД с историей версий файлов.
Советы по безопасности и соответствию (GDPR/конфиденциальность)
- Не храните персональные данные в имени файла (email, идентификаторы).
- Для персональных изображений используйте приватный bucket и signed URL с коротким TTL.
- Документируйте срок хранения файлов и реализуйте политику удаления (retention).
- Если требуется шифрование at-rest, проверьте, предоставляет ли Supabase нужные опции; при необходимости используйте шифрование на уровне приложения.
Notes: ответственность за соответствие локальным законам и GDPR лежит на владельце приложения.
Советы по производительности
- Настройте cacheControl для статичных изображений.
- Используйте CDN перед bucket, если ожидается высокий трафик.
- Массивные наборы миниатюризируйте изображение перед загрузкой на клиенте (resize) либо генерируйте превью на сервере.
Совместимость и миграция
- Next.js 12 (pages) и Next.js 13+ (app) имеют разную организацию файлов — в статье показаны примеры для App Router; для pages создавайте upload.js в папке pages.
- Существующий S3-хранилище можно оставить и подключать через собственный CDN, но перенос на Supabase потребует скриптов миграции (копирование объектов и запись путей).
Примеры дополнительных сниппетов
Получение signed URL (временная ссылка) для приватного файла:
const { data, error } = await supabase.storage
.from('images')
.createSignedUrl('path/to/file.jpg', 60) // TTL в секундах
if (error) throw error
const signedUrl = data.signedUrlУдаление файла:
const { error } = await supabase.storage.from('images').remove(['path/to/file.jpg'])
if (error) console.error(error)Список файлов в bucket:
const { data, error } = await supabase.storage.from('images').list('/', { limit: 100, offset: 0 })Пример мини-Playbook для продакшена
- Настроить мониторинг ошибок загрузки и метрик (latency, error rate).
- Настроить резервное копирование bucket и периодическую проверку целостности.
- Добавить автоматические тесты для загрузки/чтения/удаления.
- План обновления политик доступа и уведомления при изменении.
Итог и рекомендации
Использование Supabase Storage для хранения изображений — простой и масштабируемый подход для большинства веб-приложений. Он экономичнее и удобнее, чем хранение BLOB в базе данных. Тем не менее, внимательно продумайте требования по приватности, размерам файлов и требованиям к транзакциям.
Summary:
- Используйте уникальные имена файлов (UUID).
- Храните ключи в переменных окружения.
- Не используйте service_role ключи в клиентском коде.
- Для приватных изображений отдавайте signed URL.
Если нужно, могу добавить конкретный пример API-роута Next.js для генерации временного signed URL под приватные файлы или готовый миграционный скрипт для переноса файлов из S3.