nextjs.mdx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. ---
  2. slug: /nextjs
  3. ---
  4. import Tabs from '@theme/Tabs';
  5. import TabItem from '@theme/TabItem';
  6. # Next.js
  7. Integration guide for [Next.js][] featuring the [dashboard](/docs/dashboard),
  8. the [tus](/docs/tus) uploader, [transloadit](/docs/transloadit), multipart
  9. uploads to a Next.js route, the Uppy UI components, and the
  10. [React hooks](/docs/react).
  11. :::tip
  12. Uppy also has hooks and more React examples in the [React docs](/docs/react).
  13. :::
  14. ## Install
  15. <Tabs>
  16. <TabItem value="npm" label="NPM" default>
  17. ```shell
  18. npm install @uppy/core @uppy/dashboard @uppy/react
  19. ```
  20. </TabItem>
  21. <TabItem value="yarn" label="Yarn">
  22. ```shell
  23. yarn add @uppy/core @uppy/dashboard @uppy/react
  24. ```
  25. </TabItem>
  26. </Tabs>
  27. ## Tus
  28. [Tus][tus] is an open protocol for resumable uploads built on HTTP. This means
  29. accidentally closing your tab or losing connection let’s you continue, for
  30. instance, your 10GB upload instead of starting all over.
  31. Tus supports any language, any platform, and any network. It requires a client
  32. and server integration to work. We will be using [tus Node.js][].
  33. Checkout the [`@uppy/tus` docs](/docs/tus) for more information.
  34. ```tsx
  35. 'use client';
  36. import Uppy from '@uppy/core';
  37. // For now, if you do not want to install UI components you
  38. // are not using import from lib directly.
  39. import Dashboard from '@uppy/react/lib/Dashboard';
  40. import Tus from '@uppy/tus';
  41. import { useState } from 'react';
  42. import '@uppy/core/dist/style.min.css';
  43. import '@uppy/dashboard/dist/style.min.css';
  44. function createUppy() {
  45. return new Uppy().use(Tus, { endpoint: '/api/upload' });
  46. }
  47. export default function UppyDashboard() {
  48. // Important: use an initializer function to prevent the state from recreating.
  49. const [uppy] = useState(createUppy);
  50. return <Dashboard theme="dark" uppy={uppy} />;
  51. }
  52. ```
  53. [`@tus/server`][] does not not support the Next.js app router yet, which is
  54. based on the fetch `Request` API instead of `http.IncomingMessage` and
  55. `http.ServerResponse`.
  56. Even if you are fully comitting to the app router, there is no downside to still
  57. having the pages router next to it for some Node.js style API routes.
  58. Attach the tus server handler to a Next.js route handler in an
  59. [optional catch-all route file](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-routes).
  60. `/pages/api/upload/[[...file]].ts`
  61. ```ts
  62. import type { NextApiRequest, NextApiResponse } from 'next';
  63. import { Server, Upload } from '@tus/server';
  64. import { FileStore } from '@tus/file-store';
  65. /**
  66. * !Important. This will tell Next.js NOT Parse the body as tus requires
  67. * @see https://nextjs.org/docs/api-routes/request-helpers
  68. */
  69. export const config = {
  70. api: {
  71. bodyParser: false,
  72. },
  73. };
  74. const tusServer = new Server({
  75. // `path` needs to match the route declared by the next file router
  76. path: '/api/upload',
  77. datastore: new FileStore({ directory: './files' }),
  78. });
  79. export default function handler(req: NextApiRequest, res: NextApiResponse) {
  80. return tusServer.handle(req, res);
  81. }
  82. ```
  83. ## Transloadit
  84. :::note
  85. Before continuing you should have a [Transloadit](https://transloadit.com)
  86. account and a
  87. [Template](https://transloadit.com/docs/getting-started/my-first-app/) setup.
  88. :::
  89. Transloadit’s strength is versatility. By doing video, audio, images, documents,
  90. and more, you only need one vendor for [all your file processing
  91. needs][transloadit-services]. The [`@uppy/transloadit`](/docs/transloadit)
  92. plugin directly uploads to Transloadit so you only have to worry about creating
  93. a [template][transloadit-concepts]. It uses
  94. [Tus](#i-want-reliable-resumable-uploads) under the hood so you don’t have to
  95. sacrifice reliable, resumable uploads for convenience.
  96. When you go to production always make sure to set the `signature`. **Not using
  97. [Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/)
  98. can be a security risk**. Signature Authentication is a security measure that
  99. can prevent outsiders from tampering with your Assembly Instructions.
  100. Generating a signature should be done on the server to avoid leaking secrets.
  101. <Tabs>
  102. <TabItem value="app" label="App Router" default>
  103. `/app/api/transloadit/route.ts`
  104. ```ts
  105. import { NextResponse, NextRequest } from 'next/server';
  106. import crypto from 'crypto';
  107. function utcDateString(ms: number): string {
  108. return new Date(ms)
  109. .toISOString()
  110. .replace(/-/g, '/')
  111. .replace(/T/, ' ')
  112. .replace(/\.\d+Z$/, '+00:00');
  113. }
  114. export async function POST(request: NextRequest) {
  115. // expire 1 hour from now (this must be milliseconds)
  116. const expires = utcDateString(Date.now() + 1 * 60 * 60 * 1000);
  117. const authKey = process.env.TRANSLOADIT_KEY;
  118. const authSecret = process.env.TRANSLOADIT_SECRET;
  119. const templateId = process.env.TRANSLOADIT_TEMPLATE_ID;
  120. // Typically, here you would also deny generating a signature for improper use
  121. if (!authKey || !authSecret || !templateId) {
  122. return NextResponse.json(
  123. { error: 'Missing Transloadit credentials' },
  124. { status: 500 },
  125. );
  126. }
  127. const body = await request.json();
  128. const params = JSON.stringify({
  129. auth: {
  130. key: authKey,
  131. expires,
  132. },
  133. template_id: templateId,
  134. fields: {
  135. // This becomes available in your Template as `${fields.customValue}`
  136. // and could be used to have a storage directory per user for example
  137. customValue: body.customValue,
  138. },
  139. // your other params like notify_url, etc.
  140. });
  141. const signatureBytes = crypto
  142. .createHmac('sha384', authSecret)
  143. .update(Buffer.from(params, 'utf-8'));
  144. // The final signature needs the hash name in front, so
  145. // the hashing algorithm can be updated in a backwards-compatible
  146. // way when old algorithms become insecure.
  147. const signature = `sha384:${signatureBytes.digest('hex')}`;
  148. return NextResponse.json({ expires, signature, params });
  149. }
  150. ```
  151. </TabItem>
  152. <TabItem value="pages" label="Pages Router">
  153. `/pages/api/transloadit/params.ts`
  154. ```ts
  155. import type { NextApiRequest, NextApiResponse } from 'next';
  156. import crypto from 'node:crypto';
  157. function utcDateString(ms: number): string {
  158. return new Date(ms)
  159. .toISOString()
  160. .replace(/-/g, '/')
  161. .replace(/T/, ' ')
  162. .replace(/\.\d+Z$/, '+00:00');
  163. }
  164. export default function handler(req: NextApiRequest, res: NextApiResponse) {
  165. // Typically, here you would also deny generating a signature for improper use
  166. if (req.method !== 'POST') {
  167. return res.status(405).json({ error: 'Method Not Allowed' });
  168. }
  169. // expire 1 hour from now (this must be milliseconds)
  170. const expires = utcDateString(Date.now() + 1 * 60 * 60 * 1000);
  171. const authKey = process.env.TRANSLOADIT_KEY;
  172. const authSecret = process.env.TRANSLOADIT_SECRET;
  173. const templateId = process.env.TRANSLOADIT_TEMPLATE_ID;
  174. if (!authKey || !authSecret || !templateId) {
  175. return res.status(500).json({ error: 'Missing Transloadit credentials' });
  176. }
  177. const params = JSON.stringify({
  178. auth: {
  179. key: authKey,
  180. expires,
  181. },
  182. template_id: templateId,
  183. fields: {
  184. // This becomes available in your Template as `${fields.customValue}`
  185. // and could be used to have a storage directory per user for example
  186. customValue: req.body.customValue,
  187. },
  188. // your other params like notify_url, etc.
  189. });
  190. const signatureBytes = crypto
  191. .createHmac('sha384', authSecret)
  192. .update(Buffer.from(params, 'utf-8'));
  193. // The final signature needs the hash name in front, so
  194. // the hashing algorithm can be updated in a backwards-compatible
  195. // way when old algorithms become insecure.
  196. const signature = `sha384:${signatureBytes.digest('hex')}`;
  197. res.status(200).json({ expires, signature, params });
  198. }
  199. ```
  200. </TabItem>
  201. </Tabs>
  202. On the client we want to fetch the signature and params from the server. You may
  203. want to send values from React state along to your endpoint, for instance to add
  204. [`fields`](https://transloadit.com/docs/topics/assembly-variables/) which you
  205. can use in your template as global variables.
  206. ```js
  207. // ...
  208. function createUppy() {
  209. const uppy = new Uppy();
  210. uppy.use(Transloadit, {
  211. async assemblyOptions() {
  212. // You can send meta data along for use in your template.
  213. // https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions
  214. const { meta } = uppy.getState();
  215. const body = JSON.stringify({ customValue: meta.customValue });
  216. const res = await fetch('/transloadit-params', { method: 'POST', body });
  217. return response.json();
  218. },
  219. });
  220. return uppy;
  221. }
  222. function Component({ customValue }) {
  223. // IMPORTANT: passing an initializer function to prevent the state from recreating.
  224. const [uppy] = useState(createUppy);
  225. useEffect(() => {
  226. if (customValue) {
  227. uppy.setOptions({ meta: { customValue } });
  228. }
  229. }, [uppy, customValue]);
  230. }
  231. ```
  232. ## HTTP uploads to your backend
  233. If you want to handle uploads yourself, in Next.js or another server in any
  234. language, you can use [`@uppy/xhr-upload`](/docs/xhr-upload).
  235. :::warning
  236. The server-side examples are simplified for demonstration purposes and assume a
  237. regular file upload while `@uppy/xhr-upload` can also send `FormData` through
  238. the `formData` or `bundle` options.
  239. :::
  240. <Tabs>
  241. <TabItem value="app" label="App Router" default>
  242. ```ts
  243. import { NextRequest, NextResponse } from 'next/server';
  244. import { writeFile } from 'node:fs/promises';
  245. import path from 'node:path';
  246. export const config = {
  247. api: {
  248. bodyParser: false,
  249. },
  250. };
  251. export async function POST(request: NextRequest) {
  252. const formData = await request.formData();
  253. const file = formData.get('file') as File | null;
  254. if (!file) {
  255. return NextResponse.json({ error: 'No file uploaded' }, { status: 400 });
  256. }
  257. const buffer = Buffer.from(await file.arrayBuffer());
  258. const filename = file.name.replace(/\s/g, '-');
  259. const filepath = path.join(process.cwd(), 'public', 'uploads', filename);
  260. try {
  261. await writeFile(filepath, buffer);
  262. return NextResponse.json({
  263. message: 'File uploaded successfully',
  264. filename,
  265. });
  266. } catch (error) {
  267. console.error('Error saving file:', error);
  268. return NextResponse.json({ error: 'Error saving file' }, { status: 500 });
  269. }
  270. }
  271. ```
  272. </TabItem>
  273. <TabItem value="pages" label="Pages Router">
  274. ```ts
  275. import type { NextApiRequest, NextApiResponse } from 'next';
  276. import { createWriteStream } from 'fs';
  277. import { pipeline } from 'stream/promises';
  278. import path from 'path';
  279. export const config = {
  280. api: {
  281. bodyParser: false,
  282. },
  283. };
  284. export default async function handler(
  285. req: NextApiRequest,
  286. res: NextApiResponse,
  287. ) {
  288. if (req.method !== 'POST') {
  289. return res.status(405).json({ error: 'Method Not Allowed' });
  290. }
  291. try {
  292. const filename = `file-${Date.now()}.txt`;
  293. const filepath = path.join(process.cwd(), 'public', 'uploads', filename);
  294. const writeStream = createWriteStream(filepath);
  295. await pipeline(req, writeStream);
  296. res.status(200).json({ message: 'File uploaded successfully', filename });
  297. } catch (error) {
  298. console.error('Error saving file:', error);
  299. res.status(500).json({ error: 'Error saving file' });
  300. }
  301. }
  302. ```
  303. </TabItem>
  304. </Tabs>
  305. ```tsx
  306. 'use client';
  307. import Uppy from '@uppy/core';
  308. // For now, if you do not want to install UI components you
  309. // are not using import from lib directly.
  310. import Dashboard from '@uppy/react/lib/Dashboard';
  311. import Xhr from '@uppy/xhr-upload';
  312. import { useState } from 'react';
  313. import '@uppy/core/dist/style.min.css';
  314. import '@uppy/dashboard/dist/style.min.css';
  315. function createUppy() {
  316. return new Uppy().use(Xhr, { endpoint: '/api/upload' });
  317. }
  318. export default function UppyDashboard() {
  319. // Important: use an initializer function to prevent the state from recreating.
  320. const [uppy] = useState(createUppy);
  321. return <Dashboard theme="dark" uppy={uppy} />;
  322. }
  323. ```
  324. ## Next steps
  325. - Add client-side file [restrictions](/docs/uppy/#restrictions).
  326. - Upload files together with other form fields with [`@uppy/form`](/docs/form).
  327. - Use your [language of choice](/docs/locales) instead of English.
  328. - Add an [image editor](docs/image-editor) for cropping and resizing images.
  329. - Download files from remote sources, such as [Google Drive](docs/google-drive)
  330. and [Dropbox](docs/dropbox), with [Companion](/docs/companion).
  331. - Add [Golden Retriever](/docs/golden-retriever) to save selected files in your
  332. browser cache, so that if the browser crashes, or the user accidentally closes
  333. the tab, Uppy can restore everything and continue uploading as if nothing
  334. happened.
  335. [transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/
  336. [transloadit-services]: https://transloadit.com/services/
  337. [Next.js]: https://nextjs.org/
  338. [tus]: https://tus.io/
  339. [tus Node.js]: https://github.com/tus/tus-node-server
  340. [`@tus/server`]:
  341. https://github.com/tus/tus-node-server/tree/main/packages/server