Run code once per node server startup #15341
-
We basically want to initialize few application/site level parameters on the server which can be reused (at server) on each request and updated on set interval. There is a way to do this when the node server starts but as we are using next.js built-in server, it doesn't seem straightforward.
Thank you |
Beta Was this translation helpful? Give feedback.
Replies: 47 comments 103 replies
-
Hey, did you find any solution for that? I am stuck with the same issue. |
Beta Was this translation helpful? Give feedback.
-
Same here. |
Beta Was this translation helpful? Give feedback.
-
Think you need to create a custom server. Fetch the settings in the prepare method and store in a json file and use the configs in getInitialProps. |
Beta Was this translation helpful? Give feedback.
-
The only idea I've got is to make a route which is going to be pinged by curl, but that sounds ridiculous. Custom server would really suck there. |
Beta Was this translation helpful? Give feedback.
-
I think that you can set environment key |
Beta Was this translation helpful? Give feedback.
-
I think this question is even more pertinent when we want to make a website that is pre-rendered using export functionality. There should be a way to do this easily. |
Beta Was this translation helpful? Give feedback.
-
We have started to use a singleton access pattern to get around this. The resource is not initialized until you call it for the first time. Example with import { Pool } from 'pg';
import getConfig from '../config';
let pool: Pool|null = null;
export async function query(incomingQuery, params = [], config:any = {}) {
if (!pool) {
console.log('🐘 Initializing Postgres connection!');
pool = new Pool({
connectionString: getConfig().databaseUrl,
max: getConfig().databaseClients ?? 10,
});
}
const results = await pool.query(incomingQuery, params);
return results;
}
export function getPool() {
return pool;
}
export async function disconnect() {
if (pool !== null) {
console.log('😵 Disconnecting from Postgres!');
return pool.end();
}
return new Promise(() => {});
} To use, simply run this at any place in your application: import { query, disconnect } from './postgres';
const result = await query('...');
console.log(result);
await disconnect(); |
Beta Was this translation helpful? Give feedback.
-
Is there any limitation that prevents expose functions for startup and cleanup the server?
export default async function startup({ server }) {
/// Start-up logic, database connections, add middlewares to the server, etc...
console.log("Starting server...");
}
export default function cleanup() {
/// Clean-up logic
console.log("Shutting down server...");
} |
Beta Was this translation helpful? Give feedback.
-
Did anyone found out how to initialize stuff? All the solutions around involve using a custom server or other hacky stuff... |
Beta Was this translation helpful? Give feedback.
-
It's a shame that we can't deal with low level stuff like initialisation / destruction in a low level abstraction of a http server |
Beta Was this translation helpful? Give feedback.
-
Not at the startup, but maybe at the first request as a workaround? It's now possible with middleware. Probably the same issue about destorying connections described in #15341 (reply in thread) exists in middleware, too. If you don't care about deinit, this could solve your problem.
import type { NextFetchEvent, NextRequest } from 'next/server';
const firstRequestedAt = new Date();
export function middleware(req: NextRequest, ev: NextFetchEvent) {
console.log(firstRequestedAt.toISOString());
} This outputs the same ISO string at every request.
However, once you move the logic to a different file to use module cache(which most of you are going for), it does not work as expected.
const firstRequestedAt = new Date();
const getFirstRequestedAt = () => firstRequestedAt;
export { getFirstRequestedAt };
import type { NextFetchEvent, NextRequest } from 'next/server';
import { getFirstRequestedAt } from 'path to util.ts';
export function middleware(req: NextRequest, ev: NextFetchEvent) {
console.log(getFirstRequestedAt().toISOString(), 'middleware');
}
import { getFirstRequestedAt } from 'path to util.ts';
export async function getStaticProps(context) {
console.log(getFirstRequestedAt().toISOString(), 'getStaticProps');
return {
props: {},
revalidate: 1,
};
}; This outputs a different ISO string for
Correct me if I was wrong, but this must be because import type { NextFetchEvent, NextRequest } from 'next/server';
// This gets executed at every middleware initialization, not every request but quite often.
const firstRequestedAt = new Date();
export function middleware(req: NextRequest, ev: NextFetchEvent) {
console.log(firstRequestedAt.toISOString());
} |
Beta Was this translation helpful? Give feedback.
-
A specific use-case for this is tracing with open telemetry. this must be initialized before anything else. A full custom-server to support this is overkill, and comes with many downsides. A workaround is to use
Even allowing for this, As others have noted, it would be far, far simpler to have a |
Beta Was this translation helpful? Give feedback.
-
nextjs is so nice to work with until the moment when it's not |
Beta Was this translation helpful? Give feedback.
-
Please consider upvoting this RFC #45628 |
Beta Was this translation helpful? Give feedback.
-
if want run code after server is run
In this case, can anyone tell me if there is a problem? |
Beta Was this translation helpful? Give feedback.
-
None, if you dont want to use Typescript for the part you're calling.
Otherwise, it gets a bit more complicated, but possible.
I wouldn't use NextJS again, because its pretty inconvenient.
…On Fri, Apr 14, 2023, 10:07 AM GitVex ***@***.***> wrote:
I'd also like to know if there could be any problem from using it this way
—
Reply to this email directly, view it on GitHub
<#15341 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AF5QXIRQ3KJT5SHYWHDPLMDXBEAUDANCNFSM4PCWE5GQ>
.
You are receiving this because you commented.Message ID: <vercel/next.
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
After a lot of agony to get this working with modules, typescript and running scripts in the same nodejs process. I took advantage of some of the workarounds found here and made this hack that may work for more people just like it did for me. "next": "^13.3.0"boot.ts const bootedServices = {
example1: false,
example2: false,
};
const bootHandler = async () => {
// Example1 service...
if (!bootedServices.example1) {
console.log('[pages/api/_boot.ts] => EXAMPLE1');
bootedServices.example1 = await new Promise<boolean>(resolve => {
setTimeout(() => {
resolve(true);
}, 3000);
});
}
// Example2 service...
if (!bootedServices.example2) {
console.log('[pages/api/_boot.ts] => EXAMPLE2');
bootedServices.example2 = await new Promise<boolean>(resolve => {
setTimeout(() => {
resolve(true);
}, 1000);
});
}
return bootedServices;
};
export default bootHandler; pages/api/_boot.ts import type { NextApiRequest, NextApiResponse } from 'next';
import bootHandler from '@/boot';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const bootedServices = await bootHandler();
res.status(200).json({ bootedServices });
} next.config.js const {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_SERVER,
PHASE_PRODUCTION_BUILD
} = require('next/constants');
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
const bootServices = async () => {
return fetch('http://localhost:3000/api/_boot')
.then(async (res) => {
const resJson = await res.json();
return JSON.stringify(resJson.bootedServices);
}).catch(() => false);
}
module.exports = async (phase, { defaultConfig }) => {
if (process.argv.includes('dev') && phase === PHASE_DEVELOPMENT_SERVER) {
console.log('[ next.config.js (dev) ]');
const bootedServices = await bootServices();
console.log(`[ next.config.js (dev) ] => bootedServices: ${bootedServices}`);
} else if (process.argv.includes('start') && phase === PHASE_PRODUCTION_SERVER) {
console.log('[ next.config.js (start) ]');
// Timeout start
setTimeout(async () => {
const bootedServices = await bootServices();
console.log(`[ next.config.js (start) ] => bootedServices: ${bootedServices}`);
}, 1000);
} else if (process.argv.includes('build') && phase === PHASE_PRODUCTION_BUILD) {
console.log('[ next.config.js (build) ]');
// Boot into static pages? getStaticProps ?
// pages/staticpage.tsx
// import bootHandler from '@/boot';
// const bootedServices = await bootHandler();
}
return nextConfig;
}; Terminal outputnext dev ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from C:\...\.env
event - compiled client and server successfully in 651 ms (174 modules)
[ next.config.js (dev) ]
wait - compiling /api/_boot (client and server)...
event - compiled successfully in 50 ms (44 modules)
[pages/api/_boot.ts] => EXAMPLE1
[pages/api/_boot.ts] => EXAMPLE2
[ next.config.js (dev) ] => bootedServices: {"example1":true,"example2":true} next start ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from C:\...\.env
[ next.config.js (start) ]
[pages/api/_boot.ts] => EXAMPLE1
[pages/api/_boot.ts] => EXAMPLE2
[ next.config.js (start) ] => bootedServices: {"example1":true,"example2":true} |
Beta Was this translation helpful? Give feedback.
-
I believe...there is now a solution? Last week, version 13.3.2 made changes for: Looking at the history, version 13.2.0 added the initial support: Checking out the docs from last week:
I haven't tried it myself yet, but this may be what we were looking for. |
Beta Was this translation helpful? Give feedback.
-
what about simply adding |
Beta Was this translation helpful? Give feedback.
-
We have a solution for this now! // instrumentation.ts
import { init } from 'package-init'
export function register() {
init()
} Let me know if this solves your use case. Related: #45628 |
Beta Was this translation helpful? Give feedback.
-
Any idea if it'd be ok to run operations on an interval from For example:
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Hey everybody. Looks like |
Beta Was this translation helpful? Give feedback.
-
instrumentation.ts is run only once on server startup as part of server
initialization as far as I’m aware.
…On Tue, Feb 27, 2024 at 10:10 AM Tristan Rechenberger < ***@***.***> wrote:
Hey everybody. Looks like instrumentation.ts is not called when running
server actions. 😭 Is this intentional or a bug?
—
Reply to this email directly, view it on GitHub
<#15341 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AEIG6K5UNBYOXHHJGCTKSO3YVYHPFAVCNFSM4PCWE5G2U5DIOJSWCZC7NNSXTOKENFZWG5LTONUW63SDN5WW2ZLOOQ5TQNRQHAZDCMI>
.
You are receiving this because you commented.Message ID: <vercel/next.
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
We use instrumentation.ts successfully in our project, but with some limitations:
|
Beta Was this translation helpful? Give feedback.
-
Some libraries do not support dynamic imports, so |
Beta Was this translation helpful? Give feedback.
-
In case this is helpful for anyone, here's what I'm doing to call an endpoint to revalidate the cache on startup: // instrumentation.ts
let initialLoad = false;
export async function register() {
console.log('Registering instrumentation');
if (
process.env.NEXT_RUNTIME === 'nodejs' &&
typeof window === 'undefined' &&
!initialLoad
) {
initialLoad = true;
try {
console.log('Revalidating cache on startup');
await import('./revalidate-cache.js');
} catch (error) {
console.error('Error revalidating cache on startup:', error);
}
}
} |
Beta Was this translation helpful? Give feedback.
We have a solution for this now!
instrumentation
Let me know if this solves your use case.
Related: #45628