diff --git a/next.config.js b/next.config.js new file mode 100644 index 000000000..86e8fba28 --- /dev/null +++ b/next.config.js @@ -0,0 +1,44 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + images: { + formats: ['image/avif', 'image/webp'], + }, + async headers() { + return [ + { + source: '/:path*', + headers: [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on' + }, + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, + { + key: 'Referrer-Policy', + value: 'origin-when-cross-origin' + } + ], + }, + { + // Cache static assets aggressively + source: '/fonts/:path*', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=31536000, immutable' + } + ] + } + ] + }, +} + +module.exports = nextConfig diff --git a/pages/api/index.ts b/pages/api/index.ts index c4cbff234..e527f944b 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -10,29 +10,19 @@ type ApiResponse = { message?: string } -const allowCors = - (fn: (req: NextApiRequest, res: NextApiResponse) => Promise) => - async (req: NextApiRequest, res: NextApiResponse) => { +const handler = async ( + req: NextApiRequest, + res: NextApiResponse +) => { + // Handle CORS preflight + if (req.method === 'OPTIONS') { res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type') - - if (req.method === 'OPTIONS') { - res.status(200).end() - return - } - - return await fn(req, res) + res.status(200).end() + return } -const handler = async ( - req: NextApiRequest, - res: { - status: (response: number) => { - json: (response: ApiResponse) => void - } - } -) => { const timezone = (req.query.tz as string) || Time.DEFAULT_TIMEZONE const customDate = req.query.date as string const lang = req.query.lang as string | undefined @@ -52,6 +42,11 @@ const handler = async ( : undefined const time = parsedDate ? new Time(timezone, parsedDate) : new Time(timezone) + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS') + res.setHeader('Access-Control-Allow-Headers', 'Content-Type') + res.setHeader('Cache-Control', 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400') + res.status(200).json({ timezone: timezone, date: customDate @@ -62,4 +57,4 @@ const handler = async ( }) } -export default allowCors(handler) +export default handler diff --git a/pages/api/og.tsx b/pages/api/og.tsx index 166b7bdc9..3ba8a4a2e 100644 --- a/pages/api/og.tsx +++ b/pages/api/og.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { NextApiRequest, NextApiResponse } from 'next' import { ImageResponse } from '@vercel/og' import { getRandom, @@ -8,23 +9,21 @@ import { } from '../../helpers/constants' import Time from '../../helpers/time' import { Theme } from '../../helpers/themes' +import { readFileSync } from 'fs' +import { join } from 'path' -export const config = { - runtime: 'edge' -} - -const font = fetch( - new URL('../../public/fonts/GeneralSans-Bold.otf', import.meta.url) -).then((res) => res.arrayBuffer()) +const fontData = readFileSync( + join(process.cwd(), 'public/fonts/GeneralSans-Bold.otf') +) -export default async function handler(req: Request) { - const fontData = await font - const { searchParams } = new URL(req.url) - const theme = searchParams.get('theme') || Theme.Light +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const theme = (req.query.theme as string) || Theme.Light const timezone = Time.DEFAULT_TIMEZONE const time = Time.validOrNull(timezone) - return new ImageResponse( + res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800') + + const imageResponse = new ImageResponse( (
= ({ tz, now: initialNow, initialReason }) => { ) } -export const getServerSideProps: GetServerSideProps = async (context) => { - let timezone = Array.isArray(context.query.tz) - ? (context.query.tz[0] ?? Time.DEFAULT_TIMEZONE) - : context.query.tz || Time.DEFAULT_TIMEZONE - - if (!Time.zoneExists(timezone)) { - timezone = Time.DEFAULT_TIMEZONE - } - +export const getStaticProps = async () => { + const timezone = Time.DEFAULT_TIMEZONE const time = Time.validOrNull(timezone) return { props: { tz: timezone, now: time ? time.toObject() : null - } + }, + revalidate: 3600 } } diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 000000000..fdc42ebfb --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Allow: / +Crawl-delay: 1 +User-agent: * +Disallow: /api/og$ +Allow: /api$