Blog
How Documenso, an open-source Docusign alternative, handles pdf file upload in Next.js?

How Documenso, an open-source Docusign alternative, handles pdf file upload in Next.js?

In this article, we analyse how Documenso, an open-source Docusign alternative, handles pdf file upload in Next.js app router.

But first, where is the code that takes care of upload? To find that out, we first need to know where in the user interface on Documenso we upload pdf files.

Where is the file upload related code?

When you visit /documents, you see a widget shown below that lets you 
upload pdf files in the Documenso dashboard.

Since Documenso uses Next.js app router and the url ends with /documents, we are looking for a folder named documents in the Documenso source code. You will find this in the (dashboard)/documents/page.tsx, but straight away you don’t see anything that says “upload”.

export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
  await setupI18nSSR();
 
  const { user } = await getRequiredServerComponentSession();
 
  return (
    <>
      <UpcomingProfileClaimTeaser user={user} />
      <DocumentsPageView searchParams={searchParams} />
    </>
  );
}

This means, we now have to search for “upload” in DocumentsPageView and UpcomingProfileClaimTeaser. Looking at the imports used in documents/upcoming-profile-claim-teaser.tsx, there’s nothing related to “upload”, that means, it has to be in DocumentsPageView.

In the documents-page-view.tsx, you will find this below shown import at line 27.

import { UploadDocument } from './upload-document';

and this Component is the first in order in this documents-page-view.tsx, as shown below:

<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
      <UploadDocument team={currentTeam} />
 
      <div className="mt-12 flex flex-wrap items-center justify-between gap-x-4 gap-y-8">
        <div className="flex flex-row items-center">
          {team && (

Hence the reason why you see the Upload widget followed by list of documents that you may have uploaded before.

DocumentDropzone

You will find this below code at line 133 in the upload-document.tsx,
that is imported from document-dropzone.tsx

<DocumentDropzone
 className="h-[min(400px,50vh)]"
 disabled={remaining.documents === 0 || !session?.user.emailVerified}
 disabledMessage={disabledMessage}
 onDrop={onFileDrop}
 onDropRejected={onFileDropRejected}
/>

we are interested in onFileDrop function. This function has 50+ lines of code. Let’s focus only on core functionality that does the upload.

const { type, data } = await putPdfFile(file);
 
const { id: documentDataId } = await createDocumentData({
  type,
  data,
});
 
const { id } = await createDocument({
  title: file.name,
  documentDataId,
  teamId: team?.id,
});

This code is picked from onFileDrop function. putPdfFile is a function imported from lib/universal/upload/put-file.ts

putPdfFile

/**
 * Uploads a document file to the appropriate storage location and creates
 * a document data record.
 */
export const putPdfFile = async (file: File) => {
  const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
    () => false,
  );
 
  // This will prevent uploading encrypted PDFs or anything that can't be opened.
  if (!isEncryptedDocumentsAllowed) {
    await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
      console.error(`PDF upload parse error: ${e.message}`);
 
      throw new AppError('INVALID_DOCUMENT_FILE');
    });
  }
 
  if (!file.name.endsWith('.pdf')) {
    file.name = `${file.name}.pdf`;
  }
 
  const { type, data } = await putFile(file);
 
  return await createDocumentData({ type, data });
};

This function above has some checks against uploading encrypted document.

putFile

This below code is picked from upload/put-file.ts

/**
 * Uploads a file to the appropriate storage location.
 */
export const putFile = async (file: File) => {
  const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
 
  return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
    .with('s3', async () => putFileInS3(file))
    .otherwise(async () => putFileInDatabase(file));
};

so this function supports file uploads to either S3 or upload to database. Interesting.

About us:

At Thinkthroo, we study large open source projects and provide architectural guides. We have developed reusable Components, built with tailwind, that you can use in your project.

We offer Next.js, React and Node development services.

Book a meeting with us to discuss your project.

References:

  1. https://github.com/documenso/documenso/tree/main

  2. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/page.tsx

  3. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upcoming-profile-claim-teaser.tsx

  4. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx#L41

  5. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L65

  6. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L22