Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Live preview with NextJS

default discord avatar
sandrowegmann2 years ago
11

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.

  • discord user avatar
    jmikrut
    2 years ago

    Hey

    @900030932617162793

    — 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

  • default discord avatar
    just_board2 years 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

  • default discord avatar
    angelok27412 years 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.

  • default discord avatar
    sandrowegmann2 years ago

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



    @741147904017956896

    @839912789560000522

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

  • default discord avatar
    angelok27412 years 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.

  • default discord avatar
    sandrowegmann2 years 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

  • discord user avatar
    jarrod_not_jared
    2 years 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
  • default discord avatar
    sandrowegmann2 years ago

    Oh damn thanks

    @281120856527077378

    ! This is my first time really using discord haha

  • discord user avatar
    jarrod_not_jared
    2 years ago

    No prob! Was just letting you know 😃

  • default discord avatar
    angelok27412 years ago
    @900030932617162793

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

  • default discord avatar
    dotellie2 years 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);
        });
      },
    });
Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.