Like what we’re doing? Star us on GitHub!

Live preview with NextJS

Sandro Wegmann
2 months ago
15

I just recently discovered Payload and fell in love with it immediately.



I'm now trying to set up a live preview with the NextJS Frontend which is deployed on vercel. The goal would be to have 2 windows open, edit the content in payload on the left side, see the changes on the preview website in real time (small delays are fine).



My Idea was to set up a separate deploy with an auth wall on a subdomain (preview.xyz.com) that get's the data in draft mode after the user has logged in with his payload credentials. Using the autosave feature, the content on the preview page should be updated every 2 seconds.



One unsolved question is how to actually refresh the page content: I could let the page query the content every 2 seconds, or maybe set up a websocket connection to the payload express app? Since I haven't done anything similar before, I'd love to hear if anyone thinks this will work, or if there are better/simpler ways to implement this.

  • jmikrut
    Payload Team
    2 months ago

    Hey @Sandro Wegmann — I do think that a simple websocket or messaging service would be the best way to do this



    and this is on our radar to build into Payload for sure, natively, although as of right now, you could do this on your own using SocketIO or similar

  • FireGuy_42
    2 months ago

    Sandro you are doing the lords work, if you figure it out lmk. Would love to implement a live preview, I wish there was documentation on implementation of just the preview functionality with next similar to what Prismic, DatoCMS, etc... Trying to hack something together to show team proof of concept

  • AngeloK
    2 months ago

    Same here, would love this! As of right now I just refetch the draft data each 2seconds on a preview route.



    Using SocketIO seems interesting for sure.

  • Sandro Wegmann
    2 months ago

    I'll post in here when I've figured sth out 😉



    @FireGuy_42 @AngeloK I actually managed to get it working. Check my post in the Showcase Channel or hmu 🙂

  • AngeloK
    2 months ago

    Very nice! I would love to try and implement this in my project. It's SvelteKit tho, but the functionality stays the same I guess.

  • Sandro Wegmann
    2 months ago

    Yup, you just have to implement a functionality in the frontend to 1. Authenticate a user (send a login request to payload) and 2. if logged in, listen to update calls from the server. This is a bit tricky if you're using Nextjs along with SSG etc. but I don't know about sveltekit



    Here's what you have to do serverside

    // Instead of just calling app.listen
    // app.listen(process.env.PAYLOAD_PUBLIC_INTERNAL_SERVER_URL.split(':')[2]);
    
    // create an http server (or https if you aren't using a reverse proxy)
    const server = http.createServer(app)
    server.listen(process.env.PAYLOAD_PUBLIC_INTERNAL_SERVER_URL.split(':')[2], () => {
      console.log(`HTTP Server running on port ${process.env.PAYLOAD_PUBLIC_INTERNAL_SERVER_URL.split(':')[2]}`);
    });
    
    // Socket.io
    // Now pass the server instance to socket io, to create a websocket server on the same port
    const io = require('socket.io')(server, {
      cors: {
        origin: process.env.WHITELIST_ORIGINS.split(','),
      }
    });
    
    io.on('connection', (socket) => {
      console.log('New client connected');
    
      socket.on('disconnect', () => {
        // console.log('Client disconnected')
      });
    })
    
    // You can't call io.emit directly from the payload hooks, it throws errors. 
    // As a workaround, you can set up a simple endpoint that emits the update 
    app.post('/updatePreview', (req, res) => {
      console.log('update preview called')
      // Check if authorization header contains PAYLOAD_SECRET
      if (req.headers.authorization !== process.env.PAYLOAD_PUBLIC_SERVER_SECRET) {
        res.sendStatus(401)
        return
      }
      io.emit('update')
    })


    Now, in the payload hooks, add an afterChange Hook:


    if (args.doc._status !== 'published') {
                try {
                  axios({
                    method: 'post',
                    url: `${process.env.PAYLOAD_PUBLIC_INTERNAL_SERVER_URL}/updatePreview`,
                    headers: {
                      'Authorization': `${process.env.PAYLOAD_PUBLIC_SERVER_SECRET}`
                    }
                  })
                } catch (e) {
                  console.error(e)
                }
                return console.log('Not published, skipping revalidation')

    This will just call the api endpoint you've just set up and emit the update message

  • Jarrod
    Payload Team
    2 months ago

    if you write code blocks and want them to be syntax highlighted for readability, you can append the language after the 3 back ticks. For typescript it would be 3 back ticks followed by the word

    typescript
  • Sandro Wegmann
    2 months ago

    Oh damn thanks @Jarrod ! This is my first time really using discord haha

  • Jarrod
    Payload Team
    2 months ago

    No prob! Was just letting you know 😃

  • AngeloK
    last month

    @Sandro Wegmann finally had the chance to test this out. Works like a charm!

  • Ellie
    4 weeks ago

    Gosh I'm a real grave-digger here, but if anyones interested, my solution (developed accidentally in parallel with this one haha) is perhaps a bit more involved, but the authentication part is quite interesting so I figured I should probably post it. It does require the two apps (frontend and payload) to be one the same-ish URL (ie. www.example.com and cms.example.com) but works pretty well I would say!



    export const io = new Server(server, {
      cors: {
        origin: WEB_URL,
        credentials: true,
      },
      allowRequest: (req, callback) => {
        const cookies = Object.fromEntries(
          req.headers.cookie
            ?.split("; ")
            .map((cookie) => cookie.split("="))
            .filter((tuple) => tuple.length === 2) ?? []
        );
        const payloadToken = cookies["payload-token"];
        if (typeof payloadToken !== "string") {
          callback("Not signed in", false);
          return;
        }
        jwt.verify(payloadToken, payload.secret, (err) => {
          if (err) {
            callback("Invalid token", false);
            return;
          }
          callback(null, true);
        });
      },
    });
Open the post
Continue the discussion in Discord
Can't find what you're looking for?
Get help straight from the Payload team with an Enterprise License.Learn More