mirror of
https://github.com/soconnor0919/personal-website.git
synced 2025-12-11 06:14:44 -05:00
Serve PDFs locally and automate updates from GitHub
- Add scripts/update-pdfs.js to download latest PDFs - Add cv.pdf and resume.pdf to public/publications - Update build script to run update-pdfs before next build - Switch CV and resume URLs to local files in cv/page.tsx - Add .vercel to .gitignore and vercel.json for deployment config - Update Next.js to 15.5.2 in package.json and bun.lock - Update next-env.d.ts and tsconfig.json for new types and script exclusion
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -105,4 +105,5 @@ dist
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
.tern-port
|
||||
.vercel
|
||||
|
||||
22
bun.lock
22
bun.lock
@@ -27,7 +27,7 @@
|
||||
"fs": "0.0.1-security",
|
||||
"geist": "^1.4.2",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "^15.4.5",
|
||||
"next": "^15.5.2",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18.3.1",
|
||||
@@ -181,25 +181,25 @@
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.7", "", { "dependencies": { "@emnapi/core": "^1.3.1", "@emnapi/runtime": "^1.3.1", "@tybys/wasm-util": "^0.9.0" } }, "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw=="],
|
||||
|
||||
"@next/env": ["@next/env@15.4.5", "", {}, "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ=="],
|
||||
"@next/env": ["@next/env@15.5.2", "", {}, "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg=="],
|
||||
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.4.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
@@ -935,7 +935,7 @@
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"next": ["next@15.4.5", "", { "dependencies": { "@next/env": "15.4.5", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.5", "@next/swc-darwin-x64": "15.4.5", "@next/swc-linux-arm64-gnu": "15.4.5", "@next/swc-linux-arm64-musl": "15.4.5", "@next/swc-linux-x64-gnu": "15.4.5", "@next/swc-linux-x64-musl": "15.4.5", "@next/swc-win32-arm64-msvc": "15.4.5", "@next/swc-win32-x64-msvc": "15.4.5", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g=="],
|
||||
"next": ["next@15.5.2", "", { "dependencies": { "@next/env": "15.5.2", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.2", "@next/swc-darwin-x64": "15.5.2", "@next/swc-linux-arm64-gnu": "15.5.2", "@next/swc-linux-arm64-musl": "15.5.2", "@next/swc-linux-x64-gnu": "15.5.2", "@next/swc-linux-x64-musl": "15.5.2", "@next/swc-win32-arm64-msvc": "15.5.2", "@next/swc-win32-x64-msvc": "15.5.2", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q=="],
|
||||
|
||||
"node-abi": ["node-abi@3.74.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w=="],
|
||||
|
||||
|
||||
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"build": "bun run update-pdfs && next build",
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
@@ -12,7 +12,8 @@
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache"
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"update-pdfs": "bun scripts/update-pdfs.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
@@ -38,7 +39,7 @@
|
||||
"fs": "0.0.1-security",
|
||||
"geist": "^1.4.2",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "^15.4.5",
|
||||
"next": "^15.5.2",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18.3.1",
|
||||
|
||||
BIN
public/publications/cv.pdf
Normal file
BIN
public/publications/cv.pdf
Normal file
Binary file not shown.
BIN
public/publications/resume.pdf
Normal file
BIN
public/publications/resume.pdf
Normal file
Binary file not shown.
139
scripts/update-pdfs.js
Normal file
139
scripts/update-pdfs.js
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import https from "https";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const GITHUB_REPO = "soconnor0919/resume-cv";
|
||||
const PUBLIC_DIR = path.join(__dirname, "..", "public");
|
||||
const PUBLICATIONS_DIR = path.join(PUBLIC_DIR, "publications");
|
||||
|
||||
// Ensure publications directory exists
|
||||
if (!fs.existsSync(PUBLICATIONS_DIR)) {
|
||||
fs.mkdirSync(PUBLICATIONS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// PDF files to download
|
||||
const PDF_FILES = [
|
||||
{
|
||||
name: "cv.pdf",
|
||||
url: `https://github.com/${GITHUB_REPO}/releases/download/latest/cv.pdf`,
|
||||
description: "Academic CV",
|
||||
},
|
||||
{
|
||||
name: "resume.pdf",
|
||||
url: `https://github.com/${GITHUB_REPO}/releases/download/latest/resume.pdf`,
|
||||
description: "Professional Resume",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Download a file from URL to local path
|
||||
*/
|
||||
function downloadFile(url, outputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Downloading ${url}...`);
|
||||
|
||||
const file = fs.createWriteStream(outputPath);
|
||||
|
||||
https
|
||||
.get(url, (response) => {
|
||||
// Handle redirects
|
||||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
return downloadFile(response.headers.location, outputPath)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
return reject(
|
||||
new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`),
|
||||
);
|
||||
}
|
||||
|
||||
// Check content type (GitHub serves PDFs as application/octet-stream)
|
||||
const contentType = response.headers["content-type"];
|
||||
if (
|
||||
contentType &&
|
||||
!contentType.includes("application/pdf") &&
|
||||
!contentType.includes("application/octet-stream")
|
||||
) {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
return reject(
|
||||
new Error(`Expected PDF but got content-type: ${contentType}`),
|
||||
);
|
||||
}
|
||||
|
||||
response.pipe(file);
|
||||
|
||||
file.on("finish", () => {
|
||||
file.close();
|
||||
console.log(`✓ Downloaded ${path.basename(outputPath)}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
file.on("error", (err) => {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to download all PDFs
|
||||
*/
|
||||
async function updatePDFs() {
|
||||
console.log("Updating PDFs from GitHub releases...\n");
|
||||
|
||||
try {
|
||||
for (const pdf of PDF_FILES) {
|
||||
const outputPath = path.join(PUBLICATIONS_DIR, pdf.name);
|
||||
|
||||
try {
|
||||
await downloadFile(pdf.url, outputPath);
|
||||
|
||||
// Verify the file was downloaded and has content
|
||||
const stats = fs.statSync(outputPath);
|
||||
if (stats.size === 0) {
|
||||
throw new Error("Downloaded file is empty");
|
||||
}
|
||||
|
||||
console.log(` Size: ${(stats.size / 1024).toFixed(1)} KB`);
|
||||
console.log(` Path: ${path.relative(process.cwd(), outputPath)}\n`);
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed to download ${pdf.name}: ${error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.exitCode !== 1) {
|
||||
console.log("✓ All PDFs updated successfully!");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update PDFs:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script if called directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
updatePDFs();
|
||||
}
|
||||
|
||||
export { updatePDFs };
|
||||
@@ -29,11 +29,9 @@ import {
|
||||
import Link from "next/link";
|
||||
import type { PDFDocumentProxy, PDFPageProxy } from "pdfjs-dist";
|
||||
|
||||
// GitHub release URLs for PDFs
|
||||
const CV_URL =
|
||||
"https://github.com/soconnor0919/resume-cv/releases/download/latest/cv.pdf";
|
||||
const RESUME_URL =
|
||||
"https://github.com/soconnor0919/resume-cv/releases/download/latest/resume.pdf";
|
||||
// Local PDF file URLs
|
||||
const CV_URL = "/publications/cv.pdf";
|
||||
const RESUME_URL = "/publications/resume.pdf";
|
||||
|
||||
interface PDFViewerProps {
|
||||
url: string;
|
||||
@@ -76,18 +74,8 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return new Uint8Array(arrayBuffer);
|
||||
} catch (error) {
|
||||
// If direct fetch fails (e.g., CORS), try proxy
|
||||
console.warn("Direct fetch failed, trying proxy:", error);
|
||||
|
||||
const proxyUrl = `/api/pdf-proxy?url=${encodeURIComponent(pdfUrl)}`;
|
||||
const response = await fetch(proxyUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download PDF via proxy: ${response.status}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return new Uint8Array(arrayBuffer);
|
||||
console.error("Failed to download PDF:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,11 +286,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
asChild
|
||||
className="button-hover gap-2"
|
||||
>
|
||||
<Link
|
||||
href={`/api/pdf-proxy?url=${encodeURIComponent(url)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Link href={url} target="_blank" rel="noopener noreferrer">
|
||||
<Eye className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">View PDF</span>
|
||||
</Link>
|
||||
@@ -336,11 +320,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
asChild
|
||||
className="button-hover gap-2"
|
||||
>
|
||||
<Link
|
||||
href={`/api/pdf-proxy?url=${encodeURIComponent(url)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Link href={url} target="_blank" rel="noopener noreferrer">
|
||||
<Eye className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">View PDF in New Tab</span>
|
||||
</Link>
|
||||
@@ -383,11 +363,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
asChild
|
||||
className="button-hover gap-2"
|
||||
>
|
||||
<Link
|
||||
href={`/api/pdf-proxy?url=${encodeURIComponent(url)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Link href={url} target="_blank" rel="noopener noreferrer">
|
||||
<Eye className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">View PDF</span>
|
||||
</Link>
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
"**/*.js",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "drizzle.config.ts"]
|
||||
"exclude": ["node_modules", "drizzle.config.ts", "scripts/**/*"]
|
||||
}
|
||||
|
||||
36
vercel.json
Normal file
36
vercel.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"buildCommand": "bun run build",
|
||||
"installCommand": "bun install",
|
||||
"functions": {
|
||||
"src/app/api/**/*.ts": {
|
||||
"maxDuration": 30
|
||||
}
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"source": "/publications/(.*)\\.pdf",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cache-Control",
|
||||
"value": "public, max-age=86400, s-maxage=86400"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/pdf"
|
||||
},
|
||||
{
|
||||
"key": "Content-Disposition",
|
||||
"value": "inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/api/publications/:filename",
|
||||
"destination": "/publications/:filename"
|
||||
}
|
||||
],
|
||||
|
||||
"framework": "nextjs"
|
||||
}
|
||||
Reference in New Issue
Block a user