seo fixes
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ApiDocsController extends Controller
|
||||
{
|
||||
public function show(): View
|
||||
{
|
||||
$markdown = str_replace('{{BASE_URL}}', config('app.url'), $this->markdown());
|
||||
|
||||
return view('api-docs', [
|
||||
'bodyHtml' => Str::markdown($markdown, [
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]),
|
||||
'title' => 'API Documentation — '.config('app.name'),
|
||||
'description' => 'REST API reference for creating posts, categories, tags, and AI tool listings on '.config('app.name').'.',
|
||||
'canonical' => route('api.docs'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function llmsTxt(): Response
|
||||
{
|
||||
$baseUrl = config('app.url');
|
||||
|
||||
$text = <<<TXT
|
||||
# {$this->appName()}
|
||||
|
||||
> {$this->appName()} is a blog and AI tools directory with a public JSON REST API for programmatic publishing.
|
||||
|
||||
This site welcomes AI agents that have been given valid credentials by the site operator. See the API documentation for authentication and endpoint details.
|
||||
|
||||
## Docs
|
||||
|
||||
- [API documentation]({$baseUrl}/api-docs): full REST API reference (auth, posts, categories, tags, tools)
|
||||
- [Sitemap]({$baseUrl}/sitemap.xml)
|
||||
- [RSS feed]({$baseUrl}/blog/feed.xml)
|
||||
TXT;
|
||||
|
||||
return response($text, 200)->header('Content-Type', 'text/plain; charset=UTF-8');
|
||||
}
|
||||
|
||||
private function markdown(): string
|
||||
{
|
||||
return file_get_contents(resource_path('docs/api.md'));
|
||||
}
|
||||
|
||||
private function appName(): string
|
||||
{
|
||||
return config('app.name');
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ class BlogController extends Controller
|
||||
|
||||
return view('blog.index', [
|
||||
'posts' => $posts,
|
||||
'title' => config('app.name').' — Blog',
|
||||
'description' => 'Latest articles from '.config('app.name').'.',
|
||||
'title' => config('app.name').' — AI Tools Directory & Blog for Developers',
|
||||
'description' => 'Discover and compare AI tools, plus in-depth articles on AI-assisted development, automation, and building with AI agents.',
|
||||
'canonical' => route('blog.index'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class ToolController extends Controller
|
||||
'tool' => $tool,
|
||||
'related' => $related,
|
||||
'title' => $tool->name.' — AI Tools — '.config('app.name'),
|
||||
'description' => str($tool->description)->limit(160)->toString(),
|
||||
'description' => str($tool->description)->limit(155, '')->toString(),
|
||||
'canonical' => route('tools.show', $tool),
|
||||
'image' => $tool->logo_url,
|
||||
]);
|
||||
|
||||
+16
-4
@@ -79,10 +79,22 @@ class Post extends Model
|
||||
|
||||
protected function bodyHtml(): Attribute
|
||||
{
|
||||
return Attribute::get(fn () => Str::markdown($this->body, [
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]));
|
||||
return Attribute::get(function () {
|
||||
$html = Str::markdown($this->body, [
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
// The page template already renders the post title as an <h1>. If the
|
||||
// Markdown body also opens with a top-level heading repeating that
|
||||
// title, drop it so the page doesn't ship two identical <h1>s.
|
||||
return preg_replace_callback(
|
||||
'/^\s*<h1>(.*?)<\/h1>/is',
|
||||
fn ($matches) => trim(strip_tags($matches[1])) === trim($this->title) ? '' : $matches[0],
|
||||
$html,
|
||||
limit: 1
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
protected function excerptOrSummary(): Attribute
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Page;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
@@ -21,6 +22,12 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Always generate absolute URLs (route(), url(), canonical tags, sitemap,
|
||||
// JSON-LD, ...) from the configured APP_URL, not the incoming request's
|
||||
// Host header. Without this, www vs apex requests each produce
|
||||
// self-referencing canonical URLs for identical content.
|
||||
URL::forceRootUrl(config('app.url'));
|
||||
|
||||
View::composer('components.layouts.app', function ($view) {
|
||||
$view->with('footerPages', Page::published()->inFooter()->get());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user