Possibly the example contributes to the understanding of the matter.
From the Collection of examples from discourse.threejs.org
The vertices and normals can be determined in various ways to achieve appropriate effects. See the source code of the example.
function generateCap( top ) { ... }
if ( wTop && !flatTop ) { // calculate new normals at top seam
for( let j = 0; j <= g.radial; j ++ ) {
smoothEdge( ( g.radial + 1 ) * ( g.vertical + 1 ) + 1 + j, j );
}
}
function smoothEdge( idxa, idxb ) {
let v3a = new THREE.Vector3( );
let v3b = new THREE.Vector3( );
v3a.set( g.attributes.normal.getX( idxa ), g.attributes.normal.getY( idxa ), g.attributes.normal.getZ( idxa ) );
v3b.set( g.attributes.normal.getX( idxb ), g.attributes.normal.getY( idxb ), g.attributes.normal.getZ( idxb ) );
v3.addVectors( v3a, v3b ).normalize( );
g.attributes.normal.setXYZ( idxa, v3.x, v3.y, v3.z );
g.attributes.normal.setXYZ( idxb, v3.x, v3.y, v3.z );
}