Refactor environment variable handling and improve API key management
- Introduced `client.ts` and `server.ts` for structured environment variable management using `@t3-oss/env-nextjs`. - Updated references to environment variables in various files to use the new `clientEnv` and `serverEnv` objects. - Enhanced the `next.config.mjs` file to validate environment variables during build. - Added new dependencies: `@t3-oss/env-nextjs` and `jiti` for improved environment handling. - Cleaned up imports and ensured consistent usage of environment variables across the application.
This commit is contained in:
parent
adf96e516f
commit
186bd3c2cc
@ -1,9 +1,10 @@
|
||||
// app/actions.ts
|
||||
'use server';
|
||||
|
||||
import { serverEnv } from '@/env/server';
|
||||
import { xai } from '@ai-sdk/xai';
|
||||
import { generateObject } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { xai } from '@ai-sdk/xai';
|
||||
|
||||
export async function suggestQuestions(history: any[]) {
|
||||
'use server';
|
||||
@ -37,7 +38,7 @@ Do not use pronouns like he, she, him, his, her, etc. in the questions as they b
|
||||
};
|
||||
}
|
||||
|
||||
const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY;
|
||||
const ELEVENLABS_API_KEY = serverEnv.ELEVENLABS_API_KEY;
|
||||
|
||||
export async function generateSpeech(text: string, voice: 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer' = "alloy") {
|
||||
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
// /app/api/chat/route.ts
|
||||
import { z } from "zod";
|
||||
import { xai } from '@ai-sdk/xai'
|
||||
import Exa from 'exa-js'
|
||||
import {
|
||||
convertToCoreMessages,
|
||||
streamText,
|
||||
tool,
|
||||
smoothStream
|
||||
} from "ai";
|
||||
import { BlobRequestAbortedError, put } from '@vercel/blob';
|
||||
import { getGroupConfig } from "@/app/actions";
|
||||
import { serverEnv } from "@/env/server";
|
||||
import { xai } from '@ai-sdk/xai';
|
||||
import CodeInterpreter from "@e2b/code-interpreter";
|
||||
import FirecrawlApp from '@mendable/firecrawl-js';
|
||||
import { tavily } from '@tavily/core'
|
||||
import { getGroupConfig } from "@/app/actions";
|
||||
import { geolocation, ipAddress } from '@vercel/functions'
|
||||
import { tavily } from '@tavily/core';
|
||||
import { Ratelimit } from "@upstash/ratelimit"; // for deno: see above
|
||||
import { Redis } from "@upstash/redis"; // see below for cloudflare and fastly adapters
|
||||
import { BlobRequestAbortedError, put } from '@vercel/blob';
|
||||
import {
|
||||
convertToCoreMessages,
|
||||
smoothStream,
|
||||
streamText,
|
||||
tool
|
||||
} from "ai";
|
||||
import Exa from 'exa-js';
|
||||
import { z } from "zod";
|
||||
|
||||
// Allow streaming responses up to 60 seconds
|
||||
export const maxDuration = 120;
|
||||
@ -182,7 +182,7 @@ export async function POST(req: Request) {
|
||||
searchDepth: ("basic" | "advanced")[];
|
||||
exclude_domains?: string[];
|
||||
}) => {
|
||||
const apiKey = process.env.TAVILY_API_KEY;
|
||||
const apiKey = serverEnv.TAVILY_API_KEY;
|
||||
const tvly = tavily({ apiKey });
|
||||
const includeImageDescriptions = true;
|
||||
|
||||
@ -258,7 +258,7 @@ export async function POST(req: Request) {
|
||||
}),
|
||||
execute: async ({ query }: { query: string }) => {
|
||||
try {
|
||||
const exa = new Exa(process.env.EXA_API_KEY as string);
|
||||
const exa = new Exa(serverEnv.EXA_API_KEY as string);
|
||||
|
||||
const result = await exa.searchAndContents(
|
||||
query,
|
||||
@ -304,7 +304,7 @@ export async function POST(req: Request) {
|
||||
query: z.string().describe("The search query for movies/TV shows"),
|
||||
}),
|
||||
execute: async ({ query }: { query: string }) => {
|
||||
const TMDB_API_KEY = process.env.TMDB_API_KEY;
|
||||
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
|
||||
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
||||
|
||||
try {
|
||||
@ -389,7 +389,7 @@ export async function POST(req: Request) {
|
||||
description: "Get trending movies from TMDB",
|
||||
parameters: z.object({}),
|
||||
execute: async () => {
|
||||
const TMDB_API_KEY = process.env.TMDB_API_KEY;
|
||||
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
|
||||
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
||||
|
||||
try {
|
||||
@ -423,7 +423,7 @@ export async function POST(req: Request) {
|
||||
description: "Get trending TV shows from TMDB",
|
||||
parameters: z.object({}),
|
||||
execute: async () => {
|
||||
const TMDB_API_KEY = process.env.TMDB_API_KEY;
|
||||
const TMDB_API_KEY = serverEnv.TMDB_API_KEY;
|
||||
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
||||
|
||||
try {
|
||||
@ -460,7 +460,7 @@ export async function POST(req: Request) {
|
||||
}),
|
||||
execute: async ({ query }: { query: string }) => {
|
||||
try {
|
||||
const exa = new Exa(process.env.EXA_API_KEY as string);
|
||||
const exa = new Exa(serverEnv.EXA_API_KEY as string);
|
||||
|
||||
// Search academic papers with content summary
|
||||
const result = await exa.searchAndContents(
|
||||
@ -516,7 +516,7 @@ export async function POST(req: Request) {
|
||||
}),
|
||||
execute: async ({ query, no_of_results }: { query: string, no_of_results: number }) => {
|
||||
try {
|
||||
const exa = new Exa(process.env.EXA_API_KEY as string);
|
||||
const exa = new Exa(serverEnv.EXA_API_KEY as string);
|
||||
|
||||
// Simple search to get YouTube URLs only
|
||||
const searchResult = await exa.search(
|
||||
@ -545,17 +545,17 @@ export async function POST(req: Request) {
|
||||
try {
|
||||
// Fetch detailed info from our endpoints
|
||||
const [detailsResponse, captionsResponse, timestampsResponse] = await Promise.all([
|
||||
fetch(`${process.env.YT_ENDPOINT}/video-data`, {
|
||||
fetch(`${serverEnv.YT_ENDPOINT}/video-data`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: result.url })
|
||||
}).then(res => res.ok ? res.json() : null),
|
||||
fetch(`${process.env.YT_ENDPOINT}/video-captions`, {
|
||||
fetch(`${serverEnv.YT_ENDPOINT}/video-captions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: result.url })
|
||||
}).then(res => res.ok ? res.text() : null),
|
||||
fetch(`${process.env.YT_ENDPOINT}/video-timestamps`, {
|
||||
fetch(`${serverEnv.YT_ENDPOINT}/video-timestamps`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: result.url })
|
||||
@ -595,7 +595,7 @@ export async function POST(req: Request) {
|
||||
url: z.string().describe("The URL to retrieve the information from."),
|
||||
}),
|
||||
execute: async ({ url }: { url: string }) => {
|
||||
const app = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY });
|
||||
const app = new FirecrawlApp({ apiKey: serverEnv.FIRECRAWL_API_KEY });
|
||||
try {
|
||||
const content = await app.scrapeUrl(url);
|
||||
if (!content.success || !content.metadata) {
|
||||
@ -625,7 +625,7 @@ export async function POST(req: Request) {
|
||||
lon: z.number().describe("The longitude of the location."),
|
||||
}),
|
||||
execute: async ({ lat, lon }: { lat: number; lon: number }) => {
|
||||
const apiKey = process.env.OPENWEATHER_API_KEY;
|
||||
const apiKey = serverEnv.OPENWEATHER_API_KEY;
|
||||
const response = await fetch(
|
||||
`https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${apiKey}`,
|
||||
);
|
||||
@ -645,7 +645,7 @@ export async function POST(req: Request) {
|
||||
console.log("Title:", title);
|
||||
console.log("Icon:", icon);
|
||||
|
||||
const sandbox = await CodeInterpreter.create(process.env.SANDBOX_TEMPLATE_ID!);
|
||||
const sandbox = await CodeInterpreter.create(serverEnv.SANDBOX_TEMPLATE_ID!);
|
||||
const execution = await sandbox.runCode(code);
|
||||
let message = "";
|
||||
let images = [];
|
||||
@ -729,14 +729,14 @@ export async function POST(req: Request) {
|
||||
execute: async ({ query, coordinates }: { query: string; coordinates: number[] }) => {
|
||||
try {
|
||||
// Forward geocoding with Google Maps API
|
||||
const googleApiKey = process.env.GOOGLE_MAPS_API_KEY;
|
||||
const googleApiKey = serverEnv.GOOGLE_MAPS_API_KEY;
|
||||
const googleResponse = await fetch(
|
||||
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(query)}&key=${googleApiKey}`
|
||||
);
|
||||
const googleData = await googleResponse.json();
|
||||
|
||||
// Reverse geocoding with Mapbox
|
||||
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
|
||||
const mapboxToken = serverEnv.MAPBOX_ACCESS_TOKEN;
|
||||
const [lat, lng] = coordinates;
|
||||
const mapboxResponse = await fetch(
|
||||
`https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lng}&latitude=${lat}&access_token=${mapboxToken}`
|
||||
@ -805,7 +805,7 @@ export async function POST(req: Request) {
|
||||
location?: string;
|
||||
radius?: number;
|
||||
}) => {
|
||||
const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
|
||||
const mapboxToken = serverEnv.MAPBOX_ACCESS_TOKEN;
|
||||
|
||||
let proximity = '';
|
||||
if (location) {
|
||||
@ -854,9 +854,9 @@ export async function POST(req: Request) {
|
||||
from: z.string().describe("The source language (optional, will be auto-detected if not provided)."),
|
||||
}),
|
||||
execute: async ({ text, to, from }: { text: string; to: string; from?: string }) => {
|
||||
const key = process.env.AZURE_TRANSLATOR_KEY;
|
||||
const key = serverEnv.AZURE_TRANSLATOR_KEY;
|
||||
const endpoint = "https://api.cognitive.microsofttranslator.com";
|
||||
const location = process.env.AZURE_TRANSLATOR_LOCATION;
|
||||
const location = serverEnv.AZURE_TRANSLATOR_LOCATION;
|
||||
|
||||
const url = `${endpoint}/translate?api-version=3.0&to=${to}${from ? `&from=${from}` : ''}`;
|
||||
|
||||
@ -893,14 +893,14 @@ export async function POST(req: Request) {
|
||||
type: string;
|
||||
radius: number;
|
||||
}) => {
|
||||
const apiKey = process.env.TRIPADVISOR_API_KEY;
|
||||
const apiKey = serverEnv.TRIPADVISOR_API_KEY;
|
||||
let finalLat = latitude;
|
||||
let finalLng = longitude;
|
||||
|
||||
try {
|
||||
// Try geocoding first
|
||||
const geocodingData = await fetch(
|
||||
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${process.env.GOOGLE_MAPS_API_KEY}`
|
||||
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${serverEnv.GOOGLE_MAPS_API_KEY}`
|
||||
);
|
||||
|
||||
const geocoding = await geocodingData.json();
|
||||
@ -1007,7 +1007,7 @@ export async function POST(req: Request) {
|
||||
|
||||
// Get timezone for the location
|
||||
const tzResponse = await fetch(
|
||||
`https://maps.googleapis.com/maps/api/timezone/json?location=${details.latitude},${details.longitude}×tamp=${Math.floor(Date.now() / 1000)}&key=${process.env.GOOGLE_MAPS_API_KEY}`
|
||||
`https://maps.googleapis.com/maps/api/timezone/json?location=${details.latitude},${details.longitude}×tamp=${Math.floor(Date.now() / 1000)}&key=${serverEnv.GOOGLE_MAPS_API_KEY}`
|
||||
);
|
||||
const tzData = await tzResponse.json();
|
||||
const timezone = tzData.timeZoneId || 'UTC';
|
||||
@ -1131,7 +1131,7 @@ export async function POST(req: Request) {
|
||||
execute: async ({ flight_number }: { flight_number: string }) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.aviationstack.com/v1/flights?access_key=${process.env.AVIATION_STACK_API_KEY}&flight_iata=${flight_number}`
|
||||
`https://api.aviationstack.com/v1/flights?access_key=${serverEnv.AVIATION_STACK_API_KEY}&flight_iata=${flight_number}`
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { list, del, ListBlobResult } from '@vercel/blob';
|
||||
import { del, list, ListBlobResult } from '@vercel/blob';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (req.headers.get('Authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
|
||||
if (req.headers.get('Authorization') !== `Bearer ${serverEnv.CRON_SECRET}`) {
|
||||
return new NextResponse('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "next-themes"
|
||||
import { ReactNode } from "react"
|
||||
import posthog from 'posthog-js'
|
||||
import { PostHogProvider } from 'posthog-js/react'
|
||||
import { clientEnv } from "@/env/client";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import posthog from 'posthog-js';
|
||||
import { PostHogProvider } from 'posthog-js/react';
|
||||
import { ReactNode } from "react";
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
posthog.init(clientEnv.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
api_host: clientEnv.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
person_profiles: 'always',
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import { clientEnv } from "@/env/client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
interface Location {
|
||||
lat: number;
|
||||
@ -38,7 +39,7 @@ interface Place {
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || '';
|
||||
mapboxgl.accessToken = clientEnv.NEXT_PUBLIC_MAPBOX_TOKEN || '';
|
||||
|
||||
interface InteractiveMapProps {
|
||||
center: Location;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
// /app/components/map-components.tsx
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { clientEnv } from "@/env/client";
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || '';
|
||||
mapboxgl.accessToken = clientEnv.NEXT_PUBLIC_MAPBOX_TOKEN || '';
|
||||
|
||||
interface Location {
|
||||
lat: number;
|
||||
@ -171,4 +172,4 @@ const MapContainer: React.FC<MapContainerProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export { MapComponent, MapSkeleton, MapContainer };
|
||||
export { MapComponent, MapContainer, MapSkeleton };
|
||||
|
||||
16
env/client.ts
vendored
Normal file
16
env/client.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// https://env.t3.gg/docs/nextjs#create-your-schema
|
||||
import { createEnv } from '@t3-oss/env-nextjs'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const clientEnv = createEnv({
|
||||
client: {
|
||||
NEXT_PUBLIC_MAPBOX_TOKEN: z.string().min(1),
|
||||
NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1),
|
||||
NEXT_PUBLIC_POSTHOG_HOST: z.string().min(1).url(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
|
||||
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
|
||||
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
},
|
||||
})
|
||||
24
env/server.ts
vendored
Normal file
24
env/server.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// https://env.t3.gg/docs/nextjs#create-your-schema
|
||||
import { createEnv } from '@t3-oss/env-nextjs'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const serverEnv = createEnv({
|
||||
server: {
|
||||
ELEVENLABS_API_KEY: z.string().min(1),
|
||||
TAVILY_API_KEY: z.string().min(1),
|
||||
EXA_API_KEY: z.string().min(1),
|
||||
TMDB_API_KEY: z.string().min(1),
|
||||
YT_ENDPOINT: z.string().min(1),
|
||||
FIRECRAWL_API_KEY: z.string().min(1),
|
||||
OPENWEATHER_API_KEY: z.string().min(1),
|
||||
SANDBOX_TEMPLATE_ID: z.string().min(1),
|
||||
GOOGLE_MAPS_API_KEY: z.string().min(1),
|
||||
MAPBOX_ACCESS_TOKEN: z.string().min(1),
|
||||
AZURE_TRANSLATOR_KEY: z.string().min(1),
|
||||
AZURE_TRANSLATOR_LOCATION: z.string().min(1),
|
||||
TRIPADVISOR_API_KEY: z.string().min(1),
|
||||
AVIATION_STACK_API_KEY: z.string().min(1),
|
||||
CRON_SECRET: z.string().min(1),
|
||||
},
|
||||
experimental__runtimeEnv: process.env,
|
||||
})
|
||||
173
next.config.mjs
173
next.config.mjs
@ -1,85 +1,94 @@
|
||||
// https://env.t3.gg/docs/nextjs#validate-schema-on-build-(recommended)
|
||||
import { createJiti } from 'jiti'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
const jiti = createJiti(fileURLToPath(import.meta.url))
|
||||
|
||||
// Import env here to validate during build. Using jiti we can import .ts files :)
|
||||
jiti.import('./env/server')
|
||||
jiti.import('./env/client')
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ["geist"],
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/(.*)',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'DENY',
|
||||
},
|
||||
{
|
||||
key: 'Referrer-Policy',
|
||||
value: 'strict-origin-when-cross-origin',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'www.google.com',
|
||||
port: '',
|
||||
pathname: '/s2/favicons',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'api.producthunt.com',
|
||||
port: '',
|
||||
pathname: '/widgets/embed-image/v1/featured.svg',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'metwm7frkvew6tn1.public.blob.vercel-storage.com',
|
||||
port: '',
|
||||
pathname: "**"
|
||||
},
|
||||
// upload.wikimedia.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'upload.wikimedia.org',
|
||||
port: '',
|
||||
pathname: '**'
|
||||
},
|
||||
// media.theresanaiforthat.com
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'media.theresanaiforthat.com',
|
||||
port: '',
|
||||
pathname: '**'
|
||||
},
|
||||
// www.uneed.best
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'www.uneed.best',
|
||||
port: '',
|
||||
pathname: '**'
|
||||
},
|
||||
// image.tmdb.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'image.tmdb.org',
|
||||
port: '',
|
||||
pathname: '/t/p/original/**'
|
||||
},
|
||||
// image.tmdb.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'image.tmdb.org',
|
||||
port: '',
|
||||
pathname: '/**'
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
transpilePackages: ['geist'],
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/(.*)',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'DENY',
|
||||
},
|
||||
{
|
||||
key: 'Referrer-Policy',
|
||||
value: 'strict-origin-when-cross-origin',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'www.google.com',
|
||||
port: '',
|
||||
pathname: '/s2/favicons',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'api.producthunt.com',
|
||||
port: '',
|
||||
pathname: '/widgets/embed-image/v1/featured.svg',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'metwm7frkvew6tn1.public.blob.vercel-storage.com',
|
||||
port: '',
|
||||
pathname: '**',
|
||||
},
|
||||
// upload.wikimedia.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'upload.wikimedia.org',
|
||||
port: '',
|
||||
pathname: '**',
|
||||
},
|
||||
// media.theresanaiforthat.com
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'media.theresanaiforthat.com',
|
||||
port: '',
|
||||
pathname: '**',
|
||||
},
|
||||
// www.uneed.best
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'www.uneed.best',
|
||||
port: '',
|
||||
pathname: '**',
|
||||
},
|
||||
// image.tmdb.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'image.tmdb.org',
|
||||
port: '',
|
||||
pathname: '/t/p/original/**',
|
||||
},
|
||||
// image.tmdb.org
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'image.tmdb.org',
|
||||
port: '',
|
||||
pathname: '/**',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig;
|
||||
export default nextConfig
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"@radix-ui/react-switch": "^1.1.1",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@tavily/core": "^0.0.2",
|
||||
"@types/katex": "^0.16.7",
|
||||
@ -54,6 +55,7 @@
|
||||
"geist": "^1.3.1",
|
||||
"google-auth-library": "^9.14.1",
|
||||
"highlight.js": "^11.10.0",
|
||||
"jiti": "^2.4.2",
|
||||
"katex": "^0.16.11",
|
||||
"lucide-react": "^0.424.0",
|
||||
"luxon": "^3.5.0",
|
||||
|
||||
8565
pnpm-lock.yaml
8565
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user