Creating a dynamic map in the edge
A few months ago, Wes Bos tweeted "Really neat feature of the new SvelteKit website, this dynamic SVG is rendered on the edge and changes based on your location". I decided to port the code to Next.js:
Unless you are using a VPN, the image above should pin where you are. Let's check some details (or check this pull request):
Middleware
Middlewares ↗︎ were introduced in Next.js 12 and here a middleware is being used to identify where the user comes from. Middlewares can be also helpful for A/B testing, internationalization, bot protection and more.
export async function middleware(req: NextRequest) {
const { nextUrl: url, geo } = req;
const country = geo?.country || 'US';
// ...more geo data
url.searchParams.set('country', country);
return NextResponse.rewrite(url);
}
Dependencies
The map is plotted with the help of the d3-geo, d3-geo-projection and topojson-client. The d3-geo
might take you back to the geography classes. Here is a different map with the same location info:
Endpoint returning an SVG
The endpoint to return an SVG is similar to the Vue implementation, however since this is Next.js I am using getServerSideProps
to get the location information and rendering the SVG with a handler
function. The endpoint file lives in the api
folder. I chose this folder instead of the regular pages
folder since Next.js expects React components instead of content with a different mime-type (context). A short draft:
function render({ city, country, latitude, longitude }) {
// ...return a <svg>
}
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
return {
props: query,
};
};
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const svg = render(req.query);
res.setHeader('Content-Type', 'image/svg+xml');
res.end(svg);
}
Redirects
This is an optional step, but I decided to add redirects in my Next.js config to avoid the api/
part in the URLs:
async rewrites() {
return [
// Hide /api from an image URL
{
source: '/world-globe.svg',
destination: '/api/world-globe.svg',
},
{
source: '/world-map.svg',
destination: '/api/world-map.svg',
},
];
},