SuperAPI AI Directory

tus-golang-nextjs

Frontend & websites

View repository

Updated 10-15 20:31

About this skill

TUS — Go + Next.js

TUS is resumable PATCH with Upload-Offset. The browser talks directly to the Go backend. Next.js should not proxy the entire file. After PostFinish, enqueue DASH: golang-video-dash.

Stack and scope

Part Role
github.com/tus/tusd/v2 TUS handler (creation, HEAD, PATCH, concatenation if needed)
Gin Mounting the handler, auth, CORS
tus-js-client Client in App Router ("use client")
Hooks Validate meta, move file, trigger job

TUS 1.0.x: header Tus-Resumable: 1.0.0. Preserve the major of tusd already in go.mod.

Non-negotiable rules

  • Upload directly to Go (or path rewrite without buffering the body in Node).
  • Authn/authz at creation (POST) and again at the PATCH/HEAD of the same upload.
  • Limit Upload-Length and MIME (video/mp4, …) in the hook — do not trust the client.
  • CORS: expose and allow TUS headers (Tus-Resumable, Upload-Offset, Upload-Length, Upload-Metadata, Location).
  • Disk/S3: one upload = one opaque ID; original filename only in metadata.
  • PostFinish: transition status and enqueue processing; do not transcode in the hook.

Go backend (tusd + Gin)

composer, err := tusd.NewStoreComposer()
store := filestore.FileStore{Path: cfg.TusDir}
store.UseIn(composer)

handler, err := tusd.NewHandler(tusd.Config{
    BasePath:              "/files/",
    StoreComposer:         composer,
    NotifyCompleteUploads: true,
    PreUploadCreateCallback: func(hook tusd.HookEvent) error {
        // authz já feita no middleware; valide length/MIME aqui
        return nil
    },
})

r.Use(tusCORS())
g := r.Group("/files")
g.Use(Authenticate())
g.Any("/*path", gin.WrapH(http.StripPrefix("/files/", handler)))
  • BasePath and StripPrefix equal to the public path (include trailing slash as tusd expects).
  • Consume handler.CompleteUploads (or UnroutedHandler + hooks) in a goroutine with an owner — see golang-goroutines.
  • TUS metadata is Base64; decode and parse (filename, contentType, videoId) with a limited size.
  • Production: S3 store (s3store) + memorylocker/redis locker between instances; file store only in single-node/dev.
  • MaxSize in the tusd config aligned with the product (e.g. 2–10 GiB).

CORS (browser → Go)

Allow POST, HEAD, PATCH, OPTIONS, DELETE and headers:

Authorization, Cookie, Content-Type, Tus-Resumable, Upload-Length, Upload-Offset, Upload-Metadata, Upload-Defer-Length, Upload-Concat

Expose: Location, Tus-Resumable, Tus-Version, Tus-Extension, Tus-Max-Size, Upload-Offset, Upload-Length.

Access-Control-Allow-Credentials only if the session is cookie-based; otherwise Bearer in the Authorization.

Next.js frontend

Do not use a Server Action for the binary. Client component + tus-js-client:

"use client";

import { Upload } from "tus-js-client";

export function startVideoUpload(file: File, token: string) {
  return new Promise<string>((resolve, reject) => {
    const upload = new Upload(file, {
      endpoint: process.env.NEXT_PUBLIC_TUS_ENDPOINT!, // https://api.example.com/files/
      chunkSize: 5 * 1024 * 1024,
      retryDelays: [0, 1000, 3000, 5000],
      headers: { Authorization: `Bearer ${token}` },
      metadata: {
        filename: file.name,
        contentType: file.type || "video/mp4",
      },
      onError: reject,
      onSuccess: () => resolve(upload.url!),
      onProgress: (sent, total) => {
        /* percent = sent / total */
      },
    });
    upload.findPreviousUploads().then((prev) => {
      if (prev[0]) upload.resumeFromPreviousUpload(prev[0]);
      upload.start();
    });
  });
}
  • NEXT_PUBLIC_TUS_ENDPOINT points to the Go, not to /api of Next, unless it is a streaming rewrite without a body limit.
  • Fingerprint/removeFingerprintOnSuccess: resume after refresh on the same origin.
  • Pause/abort: upload.abort(); do not leave an orphan PATCH without UX.
  • Validate file.type and size also in the UI; the server is the source of truth.
  • After success: call the API/action only to “register URL/id” — metadata, not the file.

Next.js: what to avoid

Avoid Do
Route Handler that reads the File and does fetch to Go TUS Client → Go
serverActions.bodySizeLimit huge Binary outside Next
Forget OPTIONS preflight Full CORS TUS in Go
Cookie SameSite=Strict + cross-site API Bearer or SameSite=None+Secure aware

Rewrite (next.config rewrites to /files) only if not buffering; prefer API domain.

Connection to DASH

create video (API) → status=uploading
TUS POST/PATCH → bytes no store
PostFinish → status=processing → job FFmpeg DASH
ready → player usa /vod/{id}/manifest.mpd
  • Tie Upload-Metadata to an already authenticated videoId (created before TUS).
  • Do not publish the raw MP4 if the product is DASH only; treat the original as scratch and expire it.

Anti-patterns

  • Classic multipart “one 4GB POST” through Next
  • TUS without auth on PATCHes
  • Accept any Upload-Length
  • Heavy synchronous hook (FFmpeg in PostFinish)
  • CORS * with credentials
  • Trust filename for disk path

Completion criteria

  • POST/HEAD/PATCH TUS work and resume after network interruption
  • Authz on creation and continuation
  • Size/MIME limits on the server
  • Correct CORS/TUS headers in the browser
  • Next only orchestrates UI + metadata
  • PostFinish queues DASH without blocking the upload