Skip to main content

· 2 min read
Ramu Narasinga

Next.js is MIT licensed open source, popular, recommended React framework. It has 117k stars on Github at the time of writing this article.

We all have used create-next-app but have you ever wondered what happens internally when you execute npx create-next-app?

In this article, I will explain where you can find the create-next-app related code in Vercel/Next.js

Where is create-next-app code?

create-next-app package

When you navigate to Next.js, You will see a lot of files. At first, I thought “So many files, so much code, how am I ever going to understand this?”. Well, we all have been there.

I remember, when I was getting started with programming, I would spend good 2 days to understand code inside a single .js file and I have come a long way now to make attempts to understand large, FOSS stuff. Consistency is the name of the game.

Open /packages/create-next-app in the root folder.

There it is, the mystery behind create-next-app.

Don’t forget to checkout the Readme.md for create-next-app.

Conclusion

As I keep making efforts to learn from open source, I have now chosen next.js to perform codebase analysis and I will keep writing about all my findings.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· One min read
Ramu Narasinga

Since vercel/next.js has got MIT license, it is safe to create a local copy of create-next-app package in your machine to run your experiments such as trial and error attempts, meaningful console log statements etc., to understand this package better.

Create a local copy

  • Clone the Next.js repo in your machine

git clone https://github.com/vercel/next.js.git

  • Do not navigate to next.js folder yet, from the same terminal, create a new folder called create-next-app
  • Copy everything inside next.js/packages/create-next-app into this new folder.
  • Install the dependencies using the command npm install inside this newly created folder create-next-app

create-next-app local copy

Conclusion

We have seen how to create a local copy of the create-next-app package. With this setup, you can run your little experiments as you wish.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 2 min read
Ramu Narasinga

You need to do read this prerequisite: Next.js Codebase Analysis - create-next-app - Create a local folder before you continue with this next steps.

Execute the local setup

When I copied a package into a new folder and executed the npm run build command. I ran into weird issues.

pnpm install

  1. Pnpm not found

I had to install pnpm.

  1. Missing typescript

Not sure why I got this error but I was able to resolve it using this link

Missing typescript

  1. Boss fight — TS2353: Object literal may only specify known properties, and ‘retries’ does not exist in type ‘Options’.

Any typescript error is a video game boss fight to me. You get the joke!

TS error

I found few useful links listed below, but trying them did not magically fix the issue

  1. https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/60193
  2. https://stackoverflow.com/questions/64583681/object-literal-may-only-specify-known-properties-and-retries-does-not-exist-i

Let me know in the comments what you think about this issue.

But I ended up removing the retries altogether from the create-next-app.ts. Keep in mind Next.js has MIT License hence I made the bold move to make those changes and document them here in this article.

Once I removed the retries, the error was gone. I do not recommend doing this. There must be a good reason why the original authors have retries in place. I have removed it to understand the internal workings of create-next-app without spending a day or two just because I could not get retries options to be type safe.

You need to sometimes decide if the fix you are after is really worth it in the grand scheme of things.

This code ready for execution is available here

Execution

Change create-next-app to create-my-app and then run npx create-my-app.

create-my-app

Your playground is now ready for experiments.

Conclusion

I had to remove retries because of typescript error, I do not know how to fix it yet, my goal is to understand how create-next-app works and I would not worry much about an option that is about retrying. End outcome over temporary setback.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 2 min read
Ramu Narasinga

You take any project, folder and file organisation matters. In this article, I will explain the folder structure of create-next-app

If you look at the create-next-app package, it has 2 folders and 5 files. Fortunately, we are not dealing with that many files for now.

create-next-app folder structure

This does look like a package generated using npm init.

File structure

Helpers — Contains helper functions

Templates — Templates used to generate your next.js app folders and files

Readme.md — Readme file, worth checking it out

Create-app.ts — It mainly has a function called createApp. This is where the magic happens

Index.ts — This calls the createApp from the above file.

Package.json — You know what this file is about.

Tsconfig.json — Interestingly, this file’s last committed date is 3 years ago (evident from the image above)

Conclusion

create-next-app file structure is not that complicated. Who would have thought it just uses templates and creates a folder.

So far, we just looked at folder location and structure. In the upcoming articles, I am going to add comments next to each line of code where ever it makes sense to understand the code.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 3 min read
Ramu Narasinga

In the previous article, we looked at what begins the project creation process, it was run(). In this article, we will understand what run() is made of.

I will add the meaningful comments next to each line of code that needs explanation. I will also do this explanation in parts, as I want to make little contributions everyday. Discipline over motivation.

run() — part 1:

I did not mention this before, but index.ts has 519 lines of code. I do not know if there is a coding style guide where they set a limit to the number of lines a file can have. This observation is something I picked while I was scouring the Github wilderness which is full of hidden cool patterns waiting to be discovered by the passionate FOSS enthusiasts.

Open create-next-app/index.ts and navigate to the run() function.

run() function has 308 lines of code, but I will try to understand and explain at least 15–25 lines each day. Well, this might take a long time to finish understanding how next.js repo works and it is okay. My goal is to understand and learn different strategies and techniques so I can effectively improve my coding skills and learn from elite devs. I believe, there is no better way unless you are personally mentored by the next.js authors.

Please make sure you follow comments as that is where I add the explanation, find it easy for my train of thoughts.

async function run(): Promise<void> {
// a Conf object creation with projectName.
// We do not know what Conf does yet and it is okay.
const conf = new Conf({ projectName: 'create-next-app' })
// My first thought, where did the program come from?
// Let's find out by looking outside the run() function.
// We skipped Conf class but the program variable cannot be skipped.
// I know for a fact it is a global variable.
if (program.resetPreferences) {
conf.clear()
console.log(`Preferences reset successfully`)
return
}

Program — part — 1

// Commander is a package for CLI available here:
// https://www.npmjs.com/package/commander
// packageJson is from import on top of the file
// import packageJson from './package.json'
// I personally never had to import anything from package.json,
// I guess you can do it.
const program = new Commander.Command(packageJson.name)
// https://www.npmjs.com/package/commander#version-option
.version(packageJson.version)
// https://www.npmjs.com/package/commander#command-arguments
.arguments('<project-directory>')
// https://www.npmjs.com/package/commander#usage
.usage(`${green('<project-directory>')} [options]`)
// https://www.npmjs.com/package/commander#action-handler
.action((name) => {
projectPath = name
})

Conclusion:

Part 1.1 leads to run() — part 1, program — part 1 and adding the comments next to each line of code does a good job in explaining my train of thought. I will stick to it.

The code with comments is available here: https://github.com/TThroo-Dev/opensource-codebase-analysis/commit/edac99a9dd8b57e47d19561e4f746dae59898bc3

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 4 min read
Ramu Narasinga

In the previous article, I looked at a Commander to configure and accept cli options and assign it to a variable called program.

Let’s log and see what is value in the program variable by logging it inside run()

console.log(program):

  1. Add the following as your first line in run():
console.log("program:", program);
  1. Build the create-next-app
npm run build
  1. Run the following command
npx create-my-app

Please note originally it is create-next-app, but I changed to create-my-app for purely experimental and learning purposes in package.json

nextjs

Few things worth noting here is, npx create-my-app uses what is dist, so you make any changes, the projects needs to be built using npm run build to have your latest changes in build.

The following is what program has:

program: Command {
commands: [],
options: [
Option {
flags: '-V, --version',
required: false,
optional: false,
bool: true,
short: '-V',
long: '--version',
description: 'output the version number'
},
Option {
flags: '--ts, --typescript',
required: false,
optional: false,
bool: true,
short: '--ts',
long: '--typescript',
description: '\n\n Initialize as a TypeScript project. (default)\n'
},
Option {
flags: '--js, --javascript',
required: false,
optional: false,
bool: true,
short: '--js',
long: '--javascript',
description: '\n\n Initialize as a JavaScript project.\n'
},
Option {
flags: '--tailwind',
required: false,
optional: false,
bool: true,
long: '--tailwind',
description: '\n\n Initialize with Tailwind CSS config. (default)\n'
},
Option {
flags: '--eslint',
required: false,
optional: false,
bool: true,
long: '--eslint',
description: '\n\n Initialize with eslint config.\n'
},
Option {
flags: '--app',
required: false,
optional: false,
bool: true,
long: '--app',
description: '\n\n Initialize as an App Router project.\n'
},
Option {
flags: '--src-dir',
required: false,
optional: false,
bool: true,
long: '--src-dir',
description: '\n\n Initialize inside a `src/` directory.\n'
},
Option {
flags: '--import-alias <alias-to-configure>',
required: true,
optional: false,
bool: true,
long: '--import-alias',
description: '\n\n Specify import alias to use (default "@/*").\n'
},
Option {
flags: '--use-npm',
required: false,
optional: false,
bool: true,
long: '--use-npm',
description: '\n\n Explicitly tell the CLI to bootstrap the application using npm\n'
},
Option {
flags: '--use-pnpm',
required: false,
optional: false,
bool: true,
long: '--use-pnpm',
description: '\n\n Explicitly tell the CLI to bootstrap the application using pnpm\n'
},
Option {
flags: '--use-yarn',
required: false,
optional: false,
bool: true,
long: '--use-yarn',
description: '\n\n Explicitly tell the CLI to bootstrap the application using Yarn\n'
},
Option {
flags: '--use-bun',
required: false,
optional: false,
bool: true,
long: '--use-bun',
description: '\n\n Explicitly tell the CLI to bootstrap the application using Bun\n'
},
Option {
flags: '-e, --example [name]|[github-url]',
required: false,
optional: true,
bool: true,
short: '-e',
long: '--example',
description: '\n' +
'\n' +
' An example to bootstrap the app with. You can use an example name\n' +
' from the official Next.js repo or a GitHub URL. The URL can use\n' +
' any branch and/or subdirectory\n'
},
Option {
flags: '--example-path <path-to-example>',
required: true,
optional: false,
bool: true,
long: '--example-path',
description: '\n' +
'\n' +
' In a rare case, your GitHub URL might contain a branch name with\n' +
' a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar).\n' +
' In this case, you must specify the path to the example separately:\n' +
' --example-path foo/bar\n'
},
Option {
flags: '--reset-preferences',
required: false,
optional: false,
bool: true,
long: '--reset-preferences',
description: '\n\n Explicitly tell the CLI to reset any stored preferences\n'
}
],
_execs: {},
_allowUnknownOption: true,
_args: [ { required: true, name: 'project-directory', variadic: false } ],
_name: 'create-next-app',
_version: '14.0.5-canary.55',
_versionOptionName: 'version',
_events: [Object: null prototype] {
'option:version': [Function (anonymous)],
'command:*': [Function: listener],
'option:typescript': [Function (anonymous)],
'option:javascript': [Function (anonymous)],
'option:tailwind': [Function (anonymous)],
'option:eslint': [Function (anonymous)],
'option:app': [Function (anonymous)],
'option:src-dir': [Function (anonymous)],
'option:import-alias': [Function (anonymous)],
'option:use-npm': [Function (anonymous)],
'option:use-pnpm': [Function (anonymous)],
'option:use-yarn': [Function (anonymous)],
'option:use-bun': [Function (anonymous)],
'option:example': [Function (anonymous)],
'option:example-path': [Function (anonymous)],
'option:reset-preferences': [Function (anonymous)]
},
_eventsCount: 16,
_usage: '\x1B[32m<project-directory>\x1B[39m [options]',
rawArgs: [
'/Users/ramunarasinga/.nvm/versions/node/v18.17.0/bin/node',
'/Users/ramunarasinga/.npm/_npx/cde15b9888bbc9d5/node_modules/.bin/create-my-app'
],
args: []
}

Conclusion:

We looked at how to add your own code inside index.ts. I put console.log for the program variable to look at what is inside it. Don’t forget to build your project before you test your custom code.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· One min read
Ramu Narasinga

In the previous article, we logged the program variable and saw some json. In this article, let’s understand how to access an option value passed, specifically program.resetPreferences

Let’s modify the log to print program.resetPreferences

Access options passed in CLI:

  1. Add the following as your first line in run():
console.log("program.resetPreferences:", program.resetPreferences);
  1. Build the create-next-app
npm run build
  1. Run the following command
npx create-my-app --reset-preferences

You pass –reset-preferences option to the cli

options

Conclusion:

So, to find out if any of the options you configured using Commander are passed in the CLI, you can access the flag using camelCase.

Example:

program.useNpm
program.usePnpm
program.resetPreferences

I am building a platform that explains best practices used in open source by elite programmers.

Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 2 min read
Ramu Narasinga

In the previous articles, we took a little detour to understand the program variable used in run() function.

async function run(): Promise<void> {

console.log("program.resetPreferences:", program.resetPreferences);

// a Conf object creation with projectName.
// We do not know what Conf does yet and it is okay.
const conf = new Conf({ projectName: 'create-next-app' })

// My first thought, where did the program come from?
// Let’s find out by looking outside the run() function.
// We skipped Conf class but the program variable cannot be skipped.
// I know for a fact it is a global variable.
if (program.resetPreferences) {
conf.clear()
console.log(`Preferences reset successfully`)
return
}

if (typeof projectPath === 'string') {
projectPath = projectPath.trim()
}

Unknown here is Conf. Let’s find out what it is. You have knowns and unknowns and the goal is to make unknowns in your code to knowns as much as possible.

Conf

conf is a simple npm config handling for your app or module.

Let’s console.log this and find out what is in it.

Conf

  1. Prepare a build
npm run build
  1. Execute the command
npx create-my-app

This is what conf has

conf Conf {
_deserialize: [Function (anonymous)],
_serialize: [Function (anonymous)],
events: EventEmitter {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
[Symbol(kCapture)]: false
},
path: '/Users/ramunarasinga/Library/Preferences/create-next-app-nodejs/config.json'
}

I am thinking conf is used to persist some of your preferences chosen when you run create-next-app because the following is set just before closing the run function:

I initially made this tool to let command-line tools persist some data.

The above quote from the conf package documentation.

Conclusion

Conf is used to persist data such as preferences when you use command line tools

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com

· 3 min read
Ramu Narasinga

From the previous article, where you execute the create-my-app, you will find that create-my-app executes ./dist/index.js which is basically a minified version of create-next-app/index.ts. Let’s understand the code inside index.ts

What begins it all:

Open create-next-app/index.ts and go to the bottom of the file. You will see the following snippet:

run()
.then(notifyUpdate)
.catch(async (reason) => {
console.log()
console.log('Aborting installation.')
if (reason.command) {
console.log(` ${cyan(reason.command)} has failed.`)
} else {
console.log(
red('Unexpected error. Please report it as a bug:') + '\n',
reason
)
}
console.log()
await notifyUpdate()
process.exit(1)
})

run() begins it all. I used to think using .then with catch is old fashioned but looks like it is okay to use .then, it does not always have to be async/await.

Let’s understand what the code means from function names before jumping into their definition.

I will add the meaningful comments next to line of code, so it is easy for you to follow.

run()
// On successful execution of run(), call
// notifyUpdate. Not sure what notifyUpdate does yet.
.then(notifyUpdate)
// you have a reason why catch failed,
// I tend to put error as variable name,
// It makes sense to label it as reason
.catch(async (reason) => {
console.log()
// Log explains installation is aborted
// How often do you log when you encounter failure?
console.log('Aborting installation.')
// This is specifically looking for command prop
// Specificity matters when it comes to error logging
if (reason.command) {
console.log(` ${cyan(reason.command)} has failed.`)
} else {
// There is a catchall as well
// Nice!
console.log(
red('Unexpected error. Please report it as a bug:') + '\n',
reason
)
}
console.log()

// Notify update even when the installation is aborted
// This makes me wonder if it is worth writing .then()
// But promises do not execute if you don't put .then()
// Learnt it the hard way that one time when I was calling a // promise without .then() and
// started questioning my progamming abilities because I forgot // to initailise a promise with .then()
// How often do you question your programming abilties?
await notifyUpdate()
// useful links:
// https://stackoverflow.com/questions/5266152/how-to-exit-in-node-js
// https://nodejs.org/api/process.html#process
// This exits the node process with a failure
process.exit(1)
})

Cyan, red are from a package called picocolors, found on top of index.ts import.

import { cyan, green, red, yellow, bold, blue } from 'picocolors'

Conclusion:

I will add meaningful comments next to code, but I might improvise if I find a better way to explain the code. We looked at index.ts, it all begins with run(). Interesting choice of function name run instead of init. I liked the variable name for catch parameter, instead of error, it is reason. error is what I usually name the parameter of catch.

· 2 min read
Ramu Narasinga

In the previous article, we looked at conf. In this article, we study more code from index.ts

As we advance to next lines of code, the following is what you will understand

if (typeof projectPath === 'string') {
projectPath = projectPath.trim()
}

Where is projectPath coming from?

It is a global scoped variable in index.ts. projectPath is set in action callback as shown below:

const program = new Commander.Command(packageJson.name)
// https://www.npmjs.com/package/commander#version-option
.version(packageJson.version)
// https://www.npmjs.com/package/commander#command-arguments
.arguments('<project-directory>')
// https://www.npmjs.com/package/commander#usage
.usage(`${green('<project-directory>')} [options]`)
// https://www.npmjs.com/package/commander#action-handler
.action((name) => {
projectPath = name // Here
})

Let’s console.log it and see what its got.

Logged output

There is no logged information about projectPath. What are we doing wrong? Your next immediate action is checking the documentation for Commander package and specifically, you are looking for action handler.

Here is a better example: https://github.com/tj/commander.js/blob/master/examples/thank.js

If you want to name your nextjs app as part of command npx create-next-app` you can change it to npx create-next-app my-app or you can configure the name as part of prompts. More on prompts in the upcoming articles.

Conclusion:

We understood how the projectPath is set and found that it’s primary purpose is to set a name for nextjs project.

I am building a platform that explains best practices used in open source by elite programmers. Join the waitlist and I will send you the link to the tutorials once they are ready.

If you have any questions, feel free to reach out to me at ramu.narasinga@gmail.com