What's Next(.js)? | Create Cover Image with Next.js API Routes
Tags: javascript, react, webdev, tutorial
Published: 2021-08-16 01:51:51

Hi everyone! 🙋🏻‍♀️  
Welcome to the second post of **What's Next(.js)?** the series! In this post, I'll walk over the process of creating a Next.js app that creates an [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG) generating API with Next.js API routes. Starting from the project setup and ends with a working API! It is my first ever series, so wish me luck, and any comments or inputs are deeply appreciated ✨. Well, let's start!


## Before we start...
I feel the need to address the elephant in the room. In this post, we will go through how to generate SVG and display it as an image, but unfortunately, [we can't use SVG](https://indieweb.org/The-Open-Graph-protocol#Does_not_support_SVG_images) as a cover image or [OG](https://ogp.me/) image. Sorry for the *clickbait-y* title! 🙏🏼


## Steps
The whole process will be divided into these few steps:
- setup the project
- clean up some codes
- choose or design an SVG template
- write the API
- display the SVG as an image.

### Setup
First thing first, we need to create a [new Next.js app](https://nextjs.org/docs/getting-started), I used their CLI tool which is really easy to use. To get started, just type:
```bash
npx create-next-app
# or
yarn create next-app
```
Because I want to use Typescript in this project, I added `--typescript` behind the script. So, it will be like this:
```bash
npx create-next-app --typescript
# or
yarn create next-app --typescript
```
Enter your app name and it's done, so convenient! After the installation is complete, we can start running our development server:
```bash
npm run dev
# or
yarn dev
```
Congrats! 🎉 Your app is now up and running.
![next.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629035862682/eA4zFxRis.png)

### Clean Up
Now, it's time to clean up some of the boilerplate codes. What to clean is up to you, but for me, I left everything untouched aside from `pages/index.tsx`. I deleted some texts, and now the code looks like this:
```jsx
// pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>OG Image (Next.js) | Jade</title>
        <meta
          name="description"
          content="OG Image (Next.js) | Made with 🧡  by Jade"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to OG Image (<a href="https://nextjs.org">Next.js</a>) by Jade
        </h1>
        {/* DISPLAY IMAGE HERE */}
      </main>
    </div>
  );
};

export default Home;
```
Remember to change my name (Jade) into your name, and now we can continue to the next step.

### Choose an SVG Template
What do I mean by SVG template? Because we are making an SVG generator, we need a blank design to customize later on. To use this template as our base for the cover image, we should set the dimensions to **1000px x 420px** (recommended by DEV). But, if you don't want to make a cover image, you can set the sizes to anything that you like. The template is an image that is made with SVG, it can have any kind of design that you'd like. For me, I just created a white rectangle with a gradient border. You can see and copy the code here:
```xml
  <svg width="1000" height="420" viewBox="0 0 1000 420" fill="none" xmlns="http://www.w3.org/2000/svg">
    <style>
      .title { font: bolder 52px Helvetica; color: #252A31; height: 230px; display: flex; align-items: flex-start; justify-content: flex-start; }
      .subtitle { font: bold 32px Helvetica; color: #252A31; height: 50px; display: flex; align-items: flex-end; justify-content: flex-start; }
    </style>
    <rect x="10" y="10" width="980" height="400" rx="10" fill="white"/>
    <rect x="10" y="10" width="980" height="400" rx="10" stroke="url(#paint0_angular)" stroke-width="20"/>
    <foreignObject x="80" y="80" width="840" height="280">
      <div class="title" xmlns="http://www.w3.org/1999/xhtml">
        Create Cover Image with Next.js API Routes
      </div>
      <div class="subtitle" xmlns="http://www.w3.org/1999/xhtml">
        What's Next(.js)? ● dev.to/hjades
      </div>
    </foreignObject>
    <defs>
    <radialGradient id="paint0_angular"  gradientUnits="userSpaceOnUse" gradientTransform="skewX(300) scale(3)">
    <stop offset="14.5833%" stop-color="#FA5182"/>
    <stop offset="65.1042%" stop-color="#6AC6C8"/>
    <stop offset="85.4167%" stop-color="#973D99"/>
    </radialGradient>
    </defs>
  </svg>
```
This SVG code is the one to the cover image of this post 👆🏼! You can change the texts inside to your liking and see how it looks.

Before writing this project, I didn't know much about SVG, and well, I still don't. But, I now know about [`foreignObject`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)! It allows us to embed HTML into our SVG code. This is a game-changer (for me) as it allows more flexibility. We can insert a `div` element and add css styling like `flex` to create an auto aligning and overflowing content. Without this, we can still set the text alignment with [`text-anchor`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor), but it's more complicated to handle the overflow problem. The drawback of this approach is the need for a browser context, which is not a problem at all if we were to display the SVG image on a web page. But, Next.js API routes run in a Node.js environment, so `foreignObject` is not supported.

### Write the API
Before writing the API itself, we should start by making a util function that returns the SVG template. It is a simple, pure function that has **two** parameters, which are title and footer. In the code above, the title is `Create Cover Image with Next.js API Routes` and the footer is `What's Next(.js)? ● dev.to/hjades`. So, we create a new file `_svg.ts` inside the `pages/api` folder, and the underscore in front of the filename means that it's a private file and will not be used as an API endpoint.
```ts
// pages/api/_svg.ts
export default function getSvg(title: string, footer: string): string {
  const svg = `
  <svg width="1000" height="420" viewBox="0 0 1000 420" fill="none" xmlns="http://www.w3.org/2000/svg">
    <style>
      .title { font: bolder 52px Helvetica; color: #252A31; height: 230px; display: flex; align-items: flex-start; justify-content: flex-start; }
      .subtitle { font: bold 32px Helvetica; color: #252A31; height: 50px; display: flex; align-items: flex-end; justify-content: flex-start; }
    </style>
    <rect x="10" y="10" width="980" height="400" rx="10" fill="white"/>
    <rect x="10" y="10" width="980" height="400" rx="10" stroke="url(#paint0_angular)" stroke-width="20"/>
    <foreignObject x="80" y="80" width="840" height="280">
      <div class="title" xmlns="http://www.w3.org/1999/xhtml">
        ${title}
      </div>
      <div class="subtitle" xmlns="http://www.w3.org/1999/xhtml">
        ${footer}
      </div>
    </foreignObject>
    <defs>
    <radialGradient id="paint0_angular" gradientUnits="userSpaceOnUse" gradientTransform="skewX(300) scale(3)">
    <stop offset="14.5833%" stop-color="#FA5182"/>
    <stop offset="65.1042%" stop-color="#6AC6C8"/>
    <stop offset="85.4167%" stop-color="#973D99"/>
    </radialGradient>
    </defs>
  </svg>
  `;

  return svg;
}
```
The util function is done, now we can start writing our API. You'd realized that there is a file named `hello.ts` inside the `pages/api` folder, we can rename it to `index.ts` or anything that you want. An important thing to know, Next.js uses [file-system-based routing](https://nextjs.org/docs/routing/introduction) for its [pages](https://nextjs.org/docs/basic-features/pages) and also [API routes](https://nextjs.org/docs/api-routes/introduction). This means each file inside the `pages` folder is treated as a single page, and files inside the `pages/api` folder are individual API endpoints. For example, the file `pages/api/hello.ts` will result in an endpoint to `/api/hello` and `pages/api/index.ts` will map to the `/api/` endpoint. So, inside the just renamed `index.ts` file, we can write:
```ts
// pages/api/index.ts
import type { NextApiRequest, NextApiResponse } from "next";
import getSvg from "./_svg";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const svg = getSvg(String(req.query.title), String(req.query.footer));
  res.statusCode = 200;
  res.setHeader("Content-Type", "image/svg+xml");
  res.setHeader(
    "Cache-Control",
    "public, immutable, no-transform, s-maxage=31536000, max-age=31536000"
  );
  return res.end(svg);
}
```
Let's look at the code,
- we export a function `handler` because for an API route to work, we need to export a default function (you're free to choose any function name!) with **two** parameters, which are `req` and `res`
- `req` stands for the API request ([HTTP Incoming Message instance](https://nodejs.org/api/http.html#http_class_http_incomingmessage)) and `res` is the API response ([HTTP Server Response](https://nodejs.org/api/http.html#http_class_http_serverresponse)) instance
- `req` includes some useful built-in middlewares like `req.query` which is an object containing the [query string](https://en.wikipedia.org/wiki/Query_string)
- which in this case, we send our `title` and `footer` request through typing in the URL like `https://localhost:3000/api?title=TITLE&footer=FOOTER`
- the `getSvg` function will then has `TITLE` and `FOOTER` as the arguments
- after we get the `svg` template, now we can send the template over to the client with an HTTP response
- first, we set the [response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) to `200` as it indicates the request has succeeded
- second, set the response header [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) to `image/svg+xml` because we returned an SVG code
- third, set the response header [`Cache-Control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) to enable caching (read more about the settings [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives))
- last, we end the HTTP call with sending the `svg` to the client.

Let's try our new API! Go to this URL [`http://localhost:3000/api?title=This is the title&footer=and this is the footer`](http://localhost:3000/api?title=This%20is%20the%20title&footer=and%20this%20is%20the%20footer) and what do you see? Do you see this image down here? 👇🏼
![svg.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629042421871/KdkRaEKVl.png)
We can now proceed to the last step 👏🏼

### Display the SVG as Image
We want to see our newly generated image on our web app, but how? Still remember this code `{/* DISPLAY IMAGE HERE */}` right inside our `pages/index.tsx` file? We need to replace this with an `img` tag:
```html
<img alt={querystring} src={`/api${querystring}`} width="600px" />
```
See that the `src` points to our API endpoint? That's how we render our [SVG code as an image](https://css-tricks.com/using-svg/#using-svg-as-an-img). Since we want to generate different images with different inner texts, we need a way to tell our API our request. And yes, it's the `querystring`! Just like our API, our web app can also reads the [query parameter](https://nextjs.org/docs/routing/dynamic-routes) by using `useRouter` API from [`next/router`](https://nextjs.org/docs/api-reference/next/router). When we go to the URL [`http://localhost:3000/?title=This is the title&footer=and this is the footer`](http://localhost:3000/?title=This%20is%20the%20title&footer=and%20this%20is%20the%20footer), we'll have the `query` object (from `useRouter`) as:
```json
{ "title": "This is the title", "footer": "and this is the footer" }
```
To use this information, we can add this piece of code right before the `return` statement inside our `Home` component in `pages/index.tsx` file:
```jsx
// pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
// also don't forget to import useRouter
import { useRouter } from "next/router";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  // start from here
  const { query } = useRouter();
  const querystring = `?title=${query.title}&footer=${query.footer}`;
  // to here

  return (...)
};

export default Home;
```
Now, let's go to this URL ([`http://localhost:3000/?title=This is the title&footer=and this is the footer`](http://localhost:3000/?title=This%20is%20the%20title&footer=and%20this%20is%20the%20footer)) and we'll finally see our generated SVG image! 🥳
![done.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629044447797/3cDDSsssF.png)


## What's Next?
Congratulations! You've reached the end of the post, wish you can follow through the steps without any problems. If you have any questions, please feel free to leave a comment or two! In the next post, we will try to build a simple form to allow more customization, like adding more texts or even images to our generated image. See the whole plan and a bit of a back story here.

{% link hjades/what-s-next-js-prologue-1oid %}


*P.S. You're very welcome to leave your thoughts, comments, or opinions!*




[Github repo](https://github.com/hjades/og-image/tree/phase1-svg)
[Live demo](https://ogimage-git-phase1-svg-hjades.vercel.app/?title=This%20is%20the%20title&footer=and%20this%20is%20the%20footer)


[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S65MUWI)