previous_post_link() and next_post_link(), as the docs say, need to be within the loop. But what about post single? You open one post and even if you use the global query object it won't have the same query data as your post listing - giving you wierd and/or looping results.
For anyone still seeking an answer to this, I've created a simple function get_adjacent_posts() (don't get it confused with get_adjacent_post() native wordpress function) that will always get previous and next posts regardless of the query and the placement of the function.
All you need to do is provide query args array as a parameter and it will return an array with previous and next WP post objects.
function get_adjacent_posts($args) {
global $post;
$all_posts = get_posts($args);
$len = count($all_posts);
$np = null;
$cp = $post;
$pp = null;
if ($len > 1) {
for ($i=0; $i < $len; $i++) {
if ($all_posts[$i]->ID === $cp->ID) {
if (array_key_exists($i-1, $all_posts)) {
$pp = $all_posts[$i-1];
} else {
$new_key = $len-1;
$pp = $all_posts[$new_key];
while ($pp->ID === $cp->ID) {
$new_key -= 1;
$pp = $all_posts[$new_key];
}
}
if (array_key_exists($i+1, $all_posts)) {
$np = $all_posts[$i+1];
} else {
$new_key = 0;
$np = $all_posts[$new_key];
while ($pp->ID === $cp->ID) {
$new_key += 1;
$np = $all_posts[$new_key];
}
}
break;
}
}
}
return array('next' => $np, 'prev' => $pp);
}
Example usage:
$args = array(
'post_type' => 'custom_post_type',
'posts_per_page' => -1,
'order' => 'ASC',
'orderby' => 'title'
);
$adjacent = get_adjacent_posts($args);
$next_title = $adjacent['next']->post_title;
$next_image = get_the_post_thumbnail_url($adjacent['next']->ID, 'square');
$next_url = get_permalink($adjacent['next']);
$prev_title = $adjacent['prev']->post_title;
$prev_image = get_the_post_thumbnail_url($adjacent['next']->ID, 'square');
$prev_url = get_permalink($adjacent['prev']);
WARNING: This function is resource expensive, so do not use it if you have a lot of posts. It loads and iterates all of the posts from the provided query to find next and previous posts (as you can see in it's code).
There is a better way to do this, which is directly make calls to the database, but I'm way too lazy for that anyways, plus I never needed this code on more than 100 posts.
Hope you find it useful!