LoginSignup
5
3

More than 5 years have passed since last update.

Next.js と Puppeteer を heroku で動かす

Posted at

この記事はNext.js Advent Calendar 2018の 11 日目です。

Next.js の SSR 時に puppeteer を実行して入力した URL のスクリーンショットを撮るのを作ってみました。

ソースコード

ファイルツリー

.
├── pages
│   ├── index.js
│   └── screenshot.js
├── .gitignore
├── README.md
├── package.json
├── server.js
└── yarn.lock

package.json

heroku 用にscriptsheroku-postbuildを追加しています。
後、puppeteerを使うので、enginesを追加しています。

  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "heroku-postbuild": "next build",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "next": "^7.0.2",
    "puppeteer": "^1.11.0",
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "valid-url": "^1.0.9"
  },
  "engines": {
    "node": ">8.4.0"
  }

server.js

const express = require('express');
const next = require('next');
const puppeteer = require('puppeteer');
const validUrl = require('valid-url');

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app
  .prepare()
  .then(() => {
    const server = express();

    server.get('/screenshot/:url', (req, res) => {
      let { url } = req.params;

      if (!url && !validUrl.isWebUri(url)) {
        return app.render(req, res, '/screenshot', { url: undefined });
      }
      url = decodeURIComponent(url);
      console.log(`[Server] /screenshot/${url} requested!`);
      (async () => {
        const browser = await puppeteer.launch({
          args: ['--no-sandbox', '--disable-setuid-sandbox']
        });

        const page = await browser.newPage();
        await page.goto(url);
        const img = await page.screenshot({
          path: '',
          fullPage: true,
          encoding: 'base64'
        });

        await browser.close();

        return app.render(req, res, '/screenshot', { url, img });
      })();
    });

    server.get('*', (req, res) => {
      return handle(req, res);
    });

    server.listen(port, err => {
      if (err) throw err;
      console.log(`> Ready on http://localhost:${port}`);
    });
  })
  .catch(ex => {
    console.error(ex.stack);
    process.exit(1);
  });

index.js

import React, { Component } from 'react';
import Router from 'next/router';
import validUrl from 'valid-url';
export default class extends Component {
  constructor(props) {
    super(props);
  }

  handleSubmit = e => {
    e.preventDefault();

    const urlToScreenshot = decodeURIComponent(this.refs.url.value);
    if (validUrl.isWebUri(urlToScreenshot)) {
      console.log('OK:' + urlToScreenshot);
      Router.push(`/screenshot/${encodeURIComponent(urlToScreenshot)}`);
    } else {
      console.log('NG:' + urlToScreenshot);
    }
  };

  render() {
    return (
      <div>
        スクショ撮るくん
        <form onSubmit={this.handleSubmit}>
          <input type="text" ref="url" />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

screenshot.js

import React, { Component } from 'react';

export default class extends Component {
  static getInitialProps({ query: { url, img } }) {
    return { postUrl: url, base64img: img };
  }

  render() {
    return (
      <div>
        <h1>スクショURL: {this.props.postUrl}</h1>
        <img src={'data:image/png;base64,' + this.props.base64img} />
      </div>
    );
  }
}

heroku にデプロイ

heroku で puppeteer を動かすには、node.jsの buildpack と、スクリーンショットを撮るので Noto フォントを使ってる puppeteer 用の buildpack を使っています。

$ heroku create MYAPP
$ heroku buildpacks:set heroku/nodejs
$ heroku buildpacks:add https://github.com/operando/puppeteer-heroku-buildpack.git
$ git push heroku master

その他、あとがき

  • スクリーンショットを撮ること自体はできるのですが、Server 側でエラーが出てしまっています。。。
  • やり方的には、puppeteer を api 化して本来はやるべきかなと思いながら、動くものを作ろうと作ってしまいました。
  • heroku ではなく NOW を使いたかったのですが、NOW v2 ではまだ puppeteer 対応していないようで、今後対応していくみたいな isuue をみたので今後に期待です。

参考 URL

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3