Failed to Server Render (SSR)

Hi i try to use pdftron in my client-side but i use gatsbyJs who use ssr so i got this error

Failed to Server Render (SSR)
Error message:

window is not defined
File:

node_modules/@pdftron/webviewer/webviewer.min.js:1:63
Stack:

ReferenceError: window is not defined
    at eval (webpack-internal:///./node_modules/@pdftron/webviewer/webviewer.min.js:1:63)
    at Object../node_modules/@pdftron/webviewer/webviewer.min.js (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:2451:1)
    at __webpack_require__ (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:19452:42)
    at eval (webpack-internal:///./src/components/Documents/DocumentPreview/index.js:16:76)
    at Module../src/components/Documents/DocumentPreview/index.js (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:14980:1)
    at __webpack_require__ (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:19452:42)
    at eval (webpack-internal:///./src/components/Searchbar.js:23:84)
    at Module../src/components/Searchbar.js (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:15200:1)
    at __webpack_require__ (/Users/luca/Code/Code_Taff/zenlaw/js/public/render-page.js:19452:42)
    at eval (webpack-internal:///./src/components/Integrations/index.js:11:68)

did someone have already encounter this kind of problem and if yes how fix this ?
Thanks !

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:

Guides:APIs:Forums:

Hello Luca.

Thanks for reaching out.

In order to investigate this further, I will need the steps to reproduce the issue and any relevant code you have on top of WebViewer.

Best Regards,

Diego Felix
Web Software Developer
PDFTron Systems, Inc.
www.pdftron.com
CONFIDENTIALITY NOTICE: This message (and any attachment to it) is intended only for the use of the individual or entity to which it is addressed in the header, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. Any reproduction, distribution, modification or use of the contents of this message (and any attachment to it) by any individual or entity other than the intended recipient is prohibited. If you have received this communication in error, please notify us immediately and delete the original.

Hi Diego,

the step to reproduce is:

  1. implement the trial exemple of react pdftron inside a gatsby repo
  2. launch cli ‘gatsby develop’

info: the data element is a blob


import React, { useState, useEffect, useContext, useRef } from "react"
import { useSelector } from "react-redux"
import { Grid, Dialog, IconButton, Box, SvgIcon, useTheme } from "@material-ui/core"
import CloseIcon from "@fluentui/svg-icons/icons/dismiss_20_regular.svg"
import WebViewer from "@pdftron/webviewer"

import Sidebar from "./Sidebar"
import { SnackContext } from "../../SnackMessage"
import { errcodeToMessage } from "../../../state/errorReducer"
import { downloadDocumentData } from "../../../actions/document"

export default function DocumentPreview(props) {
  const { close, documentID, fromWorkflowSelection, fromWorkflowRequest } = props
  const theme = useTheme()
  const { setSnackContent } = useContext(SnackContext)
  const viewerDiv = useRef(null)
  // get it from store to have the last updated version
  const document = useSelector(state => state.documents.find(e => e.id === documentID))
  const [data, setData] = useState(document.blobData)
  
useEffect(() => {
    if (document.id && !data)
      downloadDocumentData(document.id).then(response => {
        setData(response)
      }).catch(err => {
        setSnackContent({
          severity: "error",
          message: errcodeToMessage(err).message,
        })
      })
  }, [data, document.id, setSnackContent])

 useEffect(() => {
    WebViewer(
      {
        path: '/lib',
        initialDoc: data,
      },
      viewer.current,
    ).then((instance) => {
      console.log(instance)
}), [data])

  const handleClose = () => close()

  if (!document || !data) {
    return null
  }
  return (
    <Dialog open fullWidth maxWidth="lg" onClose={handleClose} PaperProps={{ style: { borderRadius: 10, backgroundColor: "#97969C" } }}>
      <Box style={{ overflow: "hidden", height: "100%", width: "100%" }} clone>
        <Grid container wrap="nowrap">
          <Grid item>
            <Box
              position="sticky"
              left="0"
              top="0">
              <IconButton onClick={handleClose}>
                <SvgIcon viewBox="0 0 20 20" component={CloseIcon} />
              </IconButton>
            </Box>
          </Grid>
          <Grid item xs={9}>
            <Box component="div" ref={viewerDiv} style={{ height: "100vh" }} />
          </Grid>
          <Grid item xs={3}>
            <Box pb={1} pl={1} mt={-2} style={{
              backgroundColor: theme.palette.backgroundColor.secondary,
              position: "sticky",
              right: "0",
              top: " 0",
              overflowY: "scroll",
              overflowX: "hidden",
              height: "100%",
              width: "100%",
            }}>
              <Sidebar {...{ document, fromWorkflowSelection, fromWorkflowRequest, close }} />
            </Box>
          </Grid>
        </Grid>
      </Box>
    </Dialog>
  )
}

i have also try to put “@pdftron” inside the gatsby-node.js for exclude on build-html but it’s not fixing the window problem

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  actions.setWebpackConfig({
    devtool: "eval-source-map",
  })
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
          {
            test: /pdfjs-dist/,
            use: loaders.null(),
          },
          {
            test:/@pdtftron/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

Hello Luca.

From a quick glimpse, there is nothing weird with the WebViewer part of your code. With that out of the way, I guess the next lead is that WebViewer actually needs the window object to work correctly and this object is usually not available on the server-side, but only on the client-side.

I am not familiar with Gatsby myself, but there is a question with a similar issue with NextJS here javascript - Next.js PDFtron Webviewer - ReferenceError: window is not defined - Stack Overflow . The solution is basically to import webviewer dynamically.

Let me know if that solution works for you.

Best Regards,

Diego Felix
Web Software Developer
PDFTron Systems, Inc.
www.pdftron.com
CONFIDENTIALITY NOTICE: This message (and any attachment to it) is intended only for the use of the individual or entity to which it is addressed in the header, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. Any reproduction, distribution, modification or use of the contents of this message (and any attachment to it) by any individual or entity other than the intended recipient is prohibited. If you have received this communication in error, please notify us immediately and delete the original.

Hi Diego,
Yes you totally right the WebViewer need the global window at the moment gatsby build the website for be able to render but on SSR difficult to have any window…
i have see this topic on stack-overflow yesterday and try to implement the same kind of solution but tbh load my WebViewer into the UseEffect is kind of ugly and bad practice so with my co-worker we have try to do that for give a window at the loading of the pkg :

index.js :

import React, { useState, useEffect, useContext } from "react"
import { useSelector } from "react-redux"
import { Grid, Dialog, IconButton, Box, SvgIcon, useTheme } from "@material-ui/core"
import CloseIcon from "@fluentui/svg-icons/icons/dismiss_20_regular.svg"

import loadable from "@loadable/component"
const Test = loadable(() => import("./Test"))

import Sidebar from "./Sidebar"
import { SnackContext } from "../../SnackMessage"
import { errcodeToMessage } from "../../../state/errorReducer"
import { downloadDocumentData } from "../../../actions/document"

export default function DocumentPreview(props) {
  const { close, documentID, fromWorkflowSelection, fromWorkflowRequest } = props
  const theme = useTheme()
  const { setSnackContent } = useContext(SnackContext)
  // get it from store to have the last updated version
  const document = useSelector(state => state.documents.find(e => e.id === documentID))
  const [data, setData] = useState(document.blobData)

  useEffect(() => {
    if (document.id && !data)
      downloadDocumentData(document.id).then(response => {
        setData(response)
      }).catch(err => {
        setSnackContent({
          severity: "error",
          message: errcodeToMessage(err).message,
        })
      })
  }, [data, document.id, setSnackContent])

  const handleClose = () => close()

  if (!document || !data) {
    return null
  }
  return (
    <Dialog open fullWidth maxWidth="lg" onClose={handleClose} PaperProps={{ style: { borderRadius: 10, backgroundColor: "#97969C" } }}>
      <Box style={{ overflow: "hidden", height: "100%", width: "100%" }} clone>
        <Grid container wrap="nowrap">
          <Grid item>
            <Box
              position="sticky"
              left="0"
              top="0">
              <IconButton onClick={handleClose}>
                <SvgIcon viewBox="0 0 20 20" component={CloseIcon} />
              </IconButton>
            </Box>
          </Grid>
          <Grid item xs={9}>
            <Test data={data} filename={document.filename} />
          </Grid>
          <Grid item xs={3}>
            <Box pb={1} pl={1} mt={-2} style={{
              backgroundColor: theme.palette.backgroundColor.secondary,
              position: "sticky",
              right: "0",
              top: " 0",
              overflowY: "scroll",
              overflowX: "hidden",
              height: "100%",
              width: "100%",
            }}>
              <Sidebar {...{ document, fromWorkflowSelection, fromWorkflowRequest, close }} />
            </Box>
          </Grid>
        </Grid>
      </Box>
    </Dialog>
  )
}

Test.js :

import React, { useRef, useEffect } from "react"
import { Box } from "@material-ui/core"

import WebViewer from "@pdftron/webviewer"

export default function Test({ data, filename }) {
  const viewerDiv = useRef()

  useEffect(() => {
    if (viewerDiv.current)
      WebViewer({
        path: "/lib",
      }, viewerDiv.current)
        .then(instance => {

          // `myBlob` is your blob data which can come
          // from sources such as a server or the filesystem
          instance.loadDocument(data, { filename })

          const { docViewer } = instance
          docViewer.on("documentLoaded", () => {
            // perform document operations
          })
        })
  }, [data, filename])

  return (
    <Box component="div" ref={viewerDiv} style={{ height: "100vh" }} />
  )
}

Now the problem we encounter is that the WebViewer don’t find the public folder path so nothing from WebViewer i think i strongly think that’s a Gatsby problem but if somenone have encounter the same problem could you link me the topic ?
There is a gyazo of the console:

Image from Gyazo

Best Regards,

Luca Hermann
Front-End Developer
Zenlaw, Inc.

Hi, Luca.

Yeah, it definitely seems that the file that instantiates WebViewer can’t find the lib folder. Are you using the relative path from Test.js to the lib folder?

Best Regards,

Diego Felix
Web Software Developer
PDFTron Systems, Inc.
www.pdftron.com
CONFIDENTIALITY NOTICE: This message (and any attachment to it) is intended only for the use of the individual or entity to which it is addressed in the header, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. Any reproduction, distribution, modification or use of the contents of this message (and any attachment to it) by any individual or entity other than the intended recipient is prohibited. If you have received this communication in error, please notify us immediately and delete the original.

Hi Diego.

With the relative path i get rid of the error but the viewer don’t load and render absolutely nothing to my DOM

Image from Gyazo

i do suppose is the blob i try to pass to the viewer who is not good gonna investigate on this.

Best Regards,

Luca Hermann
Front-End Developer
Zenlaw, Inc.

i encounter the same problem with a .pdf located inside js/public/files/

PdfViewer.js:

import React, { useRef, useEffect } from "react"
import { Box } from "@material-ui/core"

import WebViewer from "@pdftron/webviewer"

export default function PdfTron({ data, filename }) {
  const viewerDiv = useRef()

  useEffect(() => {
    if (viewerDiv.current)
      WebViewer({
        path: "js/public/lib",
        initialDoc: "js/public/files/PDFViewerR&d.pdf",
      }, viewerDiv.current)
        .then(instance => {
          // `myBlob` is your blob data which can come
          // from sources such as a server or the filesystem
          // instance.loadDocument(data, { filename })
          const { docViewer } = instance
          docViewer.on("documentLoaded", () => {
            // perform document operations
          })
          console.log(instance)
        })
  }, [data, filename])

  return (
    <Box component="div" ref={viewerDiv} style={{ height: "100vh" }} />
  )
}

there is no more problem with the lib folder apparently i get no error but there is nothing render to my DOM inside the iframe created by WebViewer

Gatsby need this for load properly the iframe properly with the good path to “webviewer”

gatsby-node.js:

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.com/docs/node-apis/
 */
const express = require("express")

// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
  const { createPage } = actions
  // Only update the `/app` page.
  if (page.path.match(/^\/app/)) {
    // page.matchPath is a special key that's used for matching pages
    // with corresponding routes only on the client.
    page.matchPath = "/app/*"
    // Update the page.
    createPage(page)
  }
}

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  actions.setWebpackConfig({
    devtool: "eval-source-map",
  })
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
          {
            test: /pdfjs-dist/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}

exports.onCreateDevServer = ({ app }) => {
  app.use(express.static("public"))
}

index.js:

import React, { useState, useEffect, useContext, useRef } from "react"
import { useSelector } from "react-redux"
import { Grid, Dialog, IconButton, Box, SvgIcon, useTheme } from "@material-ui/core"
//import loadable from "@loadable/component"
import CloseIcon from "@fluentui/svg-icons/icons/dismiss_20_regular.svg"

//const PdfViewer = loadable(() => import("./PdfViewer"))

import Sidebar from "./Sidebar"
import { SnackContext } from "../../SnackMessage"
import { errcodeToMessage } from "../../../state/errorReducer"
import { downloadDocumentData } from "../../../actions/document"

export default function DocumentPreview(props) {
  const { close, documentID, fromWorkflowSelection, fromWorkflowRequest } = props
  const theme = useTheme()
  const viewerDiv = useRef()
  const { setSnackContent } = useContext(SnackContext)
  // get it from store to have the last updated version
  const document = useSelector(state => state.documents.find(e => e.id === documentID))
  const [data, setData] = useState(document.blobData)

  useEffect(() => {
    if (document.id && !data)
      downloadDocumentData(document.id).then(response => {
        setData(response)
      }).catch(err => {
        setSnackContent({
          severity: "error",
          message: errcodeToMessage(err).message,
        })
      })
  }, [data, document.id, setSnackContent])

  useEffect(() => {
    if (data)
      import("@pdftron/webviewer").then(WebViewerModule => {
        let WebViewer = WebViewerModule.default
        WebViewer({
          path: "webviewer",
        }, viewerDiv.current)
          .then(instance => {
            // `myBlob` is your blob data which can come
            // from sources such as a server or the filesystem
            instance.loadDocument(data, { filename: document.filename })
            const { docViewer } = instance
            docViewer.on("documentLoaded", () => {
              // perform document operations
            })
            console.log(instance)
          })
      }).catch(err => { console.log("err on import", err) })
  }, [data, document.filename])

  const handleClose = () => close()

  if (!document || !data) {
    return null
  }
  return (
    <Dialog open fullWidth maxWidth="lg" onClose={handleClose} PaperProps={{ style: { borderRadius: 10, backgroundColor: "#97969C" } }}>
      <Box style={{ overflow: "hidden", height: "100%", width: "100%" }} clone>
        <Grid container wrap="nowrap">
          <Grid item>
            <Box
              position="sticky"
              left="0"
              top="0">
              <IconButton onClick={handleClose}>
                <SvgIcon viewBox="0 0 20 20" component={CloseIcon} />
              </IconButton>
            </Box>
          </Grid>
          <Grid item xs={9}>
            {/*<PdfViewer data={data} filename={document.filename} />*/}
            <Box component="div" ref={viewerDiv} style={{ height: "100vh" }} />
          </Grid>
          <Grid item xs={3}>
            <Box pb={1} pl={1} mt={-2} style={{
              backgroundColor: theme.palette.backgroundColor.secondary,
              position: "sticky",
              right: "0",
              top: " 0",
              overflowY: "scroll",
              overflowX: "hidden",
              height: "100%",
              width: "100%",
            }}>
              <Sidebar {...{ document, fromWorkflowSelection, fromWorkflowRequest, close }} />
            </Box>
          </Grid>
        </Grid>
      </Box>
    </Dialog>
  )
}

Hi, Luca.

Not sure if there is still an issue or you made it work. How is it going so far?

Best Regards,

Diego Felix
Web Software Developer
PDFTron Systems, Inc.
www.pdftron.com
CONFIDENTIALITY NOTICE: This message (and any attachment to it) is intended only for the use of the individual or entity to which it is addressed in the header, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. Any reproduction, distribution, modification or use of the contents of this message (and any attachment to it) by any individual or entity other than the intended recipient is prohibited. If you have received this communication in error, please notify us immediately and delete the original.

Hi, Diego,
First thanks for your time and your help.
Yeah it’s working but you should consider opening a issue on pdftron and update your documentation for an Gatsby implementation that would help a lot of dev and save time !

Best Regards,

Luca Hermann
Front-End Developer
Zenlaw, Inc.

Hi, Luca.

Thanks for the feedback!

Best Regards,

Diego Felix
Web Software Developer
PDFTron Systems, Inc.
www.pdftron.com
CONFIDENTIALITY NOTICE: This message (and any attachment to it) is intended only for the use of the individual or entity to which it is addressed in the header, and may contain information that is privileged, confidential and exempt from disclosure under applicable law. Any reproduction, distribution, modification or use of the contents of this message (and any attachment to it) by any individual or entity other than the intended recipient is prohibited. If you have received this communication in error, please notify us immediately and delete the original.