Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ What it proves:
Required:
- `TRANSLOADIT_KEY`
- `TRANSLOADIT_SECRET`

Optional:
- `TRANSLOADIT_SMARTCDN_WORKSPACE`
- `TRANSLOADIT_SMARTCDN_TEMPLATE`
- `TRANSLOADIT_SMARTCDN_INPUT`

The workspace/template/input triplet must identify a deliberately configured asset-delivery
contract. Do not use a shared Template that accepts an arbitrary origin URL.

This scenario will load `../../.env` automatically as a local-only convenience. Real apps should use their own `.env.local` and normal Next.js env loading.

## Run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ export async function GET() {
const authKey = getRequiredEnv('TRANSLOADIT_KEY');
const authSecret = getRequiredEnv('TRANSLOADIT_SECRET');

// These values don't need to exist for signing, but real deployments should set them.
const workspace = process.env.TRANSLOADIT_SMARTCDN_WORKSPACE || 'demo';
const template = process.env.TRANSLOADIT_SMARTCDN_TEMPLATE || 'serve-preview';
const input = process.env.TRANSLOADIT_SMARTCDN_INPUT || 'example.jpg';
const workspace = getRequiredEnv('TRANSLOADIT_SMARTCDN_WORKSPACE');
const template = getRequiredEnv('TRANSLOADIT_SMARTCDN_TEMPLATE');
const input = getRequiredEnv('TRANSLOADIT_SMARTCDN_INPUT');

const url = getSignedSmartCdnUrl({
workspace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ export function ensureTransloaditEnv(): void {
if (loaded) return;
loaded = true;

if (process.env.TRANSLOADIT_KEY && process.env.TRANSLOADIT_SECRET) return;

dotenv.config({
path: path.resolve(process.cwd(), '../../.env'),
quiet: true,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { chromium } from 'playwright';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { ensureTransloaditEnv } from '../../src/lib/transloadit-env';

const smartCdnConfiguration = {
input: 'images/example.jpg',
template: 'asset-delivery',
workspace: 'skill-e2e',
};

const getFreePort = async (): Promise<number> =>
await new Promise((resolve, reject) => {
const server = net.createServer();
Expand Down Expand Up @@ -44,6 +50,9 @@ describe('Smart CDN signing scenario', () => {
env: {
...process.env,
NEXT_TELEMETRY_DISABLED: '1',
TRANSLOADIT_SMARTCDN_INPUT: smartCdnConfiguration.input,
TRANSLOADIT_SMARTCDN_TEMPLATE: smartCdnConfiguration.template,
TRANSLOADIT_SMARTCDN_WORKSPACE: smartCdnConfiguration.workspace,
},
stdio: 'ignore',
},
Expand Down Expand Up @@ -88,9 +97,14 @@ describe('Smart CDN signing scenario', () => {
expect(text).toBeTruthy();
const payload = JSON.parse(text as string) as Record<string, unknown>;

expect(payload).toMatchObject(smartCdnConfiguration);
expect(typeof payload.url).toBe('string');
const url = new URL(payload.url as string);
expect(url.protocol).toBe('https:');
expect(url.hostname).toBe(`${smartCdnConfiguration.workspace}.tlcdn.com`);
expect(url.pathname).toBe(
`/${smartCdnConfiguration.template}/${encodeURIComponent(smartCdnConfiguration.input)}`,
);
// Signature parameter name may evolve; check for a common one.
expect(url.searchParams.has('sig') || url.searchParams.has('signature')).toBe(true);
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
- E2E must load `/smartcdn`, read `[data-testid="smartcdn-json"]`, parse JSON, and assert it contains a `url` string.
- The URL host and path must contain the explicitly configured workspace, Template, and input. The
implementation must not fall back to an implicit delivery contract.
- The `url` should look signed (do not snapshot secrets; just assert it contains signature-ish markers like `~` or query params).
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
---
name: integrate-asset-delivery-with-transloadit-smartcdn-in-nextjs
description: Add Transloadit Smart CDN URL signing to a Next.js App Router project (server-side signing route + optional client demo page).
description: Add server-side Transloadit Smart CDN URL signing for an explicitly configured asset to a Next.js App Router project.
---

# Inputs

- Required env (server-only): `TRANSLOADIT_KEY`, `TRANSLOADIT_SECRET`
- Optional env: `TRANSLOADIT_SMARTCDN_WORKSPACE`, `TRANSLOADIT_SMARTCDN_TEMPLATE`, `TRANSLOADIT_SMARTCDN_INPUT`
- Required env (server-only): `TRANSLOADIT_KEY`, `TRANSLOADIT_SECRET`,
`TRANSLOADIT_SMARTCDN_WORKSPACE`, `TRANSLOADIT_SMARTCDN_TEMPLATE`, and
`TRANSLOADIT_SMARTCDN_INPUT`

For local dev, put these in `.env.local`. Never expose `TRANSLOADIT_SECRET` to the browser.

Use a Template owned by `TRANSLOADIT_SMARTCDN_WORKSPACE` and give `TRANSLOADIT_SMARTCDN_INPUT` a
fixed asset or storage path. Do not use a shared Template that accepts an arbitrary origin URL. If
a workspace Template imports over HTTP, constrain its origin and path in the Template instead of
accepting an arbitrary URL from the request.

# Install

```bash
Expand All @@ -32,20 +38,19 @@ import { getSignedSmartCdnUrl } from '@transloadit/utils/node'

export const runtime = 'nodejs'

function reqEnv(name: string): string {
const v = process.env[name]
if (!v) throw new Error(`Missing required env var: ${name}`)
return v
function getRequiredEnv(name: string): string {
const value = process.env[name]
if (!value) throw new Error(`Missing required env var: ${name}`)
return value
}

export async function GET() {
try {
const authKey = reqEnv('TRANSLOADIT_KEY')
const authSecret = reqEnv('TRANSLOADIT_SECRET')

const workspace = process.env.TRANSLOADIT_SMARTCDN_WORKSPACE || 'demo'
const template = process.env.TRANSLOADIT_SMARTCDN_TEMPLATE || 'serve-preview'
const input = process.env.TRANSLOADIT_SMARTCDN_INPUT || 'example.jpg'
const authKey = getRequiredEnv('TRANSLOADIT_KEY')
const authSecret = getRequiredEnv('TRANSLOADIT_SECRET')
const workspace = getRequiredEnv('TRANSLOADIT_SMARTCDN_WORKSPACE')
const template = getRequiredEnv('TRANSLOADIT_SMARTCDN_TEMPLATE')
const input = getRequiredEnv('TRANSLOADIT_SMARTCDN_INPUT')

const url = getSignedSmartCdnUrl({ workspace, template, input, authKey, authSecret })

Expand All @@ -57,6 +62,10 @@ export async function GET() {
}
```

This route signs one server-configured public asset. Do not replace `workspace`, `template`, or
`input` with unchecked query/body values. For private assets, authenticate and authorize the caller
before returning a short-lived URL.

## 2) Optional: a tiny demo page

Create `app/smartcdn/page.tsx` (or `src/app/smartcdn/page.tsx`):
Expand Down Expand Up @@ -110,7 +119,7 @@ export default function SmartCdnDemo() {
# Quick Check

- Start dev server, then open `/smartcdn` or fetch `/api/smartcdn`.
- Expect JSON including a `url` and your `{ workspace, template, input }`.
- Expect JSON including a `url` and the configured `{ workspace, template, input }`.

# References (Internal)

Expand Down
Loading