user(); if (! $user || ! $user->is_super) { abort(403, '관리자 권한이 필요합니다.'); } return view('dev-dashboard'); })->name('web.dev'); // Admin 라우트 - admin 템플릿 의존성 검증 Route::prefix('admin') ->middleware('template.dependencies:admin') ->group(function () { Route::get('/{any?}', function (Request $request) { // 미등록 경로는 SPA 셸 본문 + HTTP 404 (soft 404 방지 — 공개#47). // 본문은 그대로라 클라이언트가 404 레이아웃을 렌더하고, 봇은 404 색인 제외. $path = '/'.ltrim($request->getPathInfo(), '/'); if (! app(TemplateRouteResolver::class)->routeExists($path, 'admin')) { return response(view('admin'), 404); } return view('admin'); })->where('any', '(?!.*\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)).*'); }); // Sitemap XML 라우트 // 자식 경로는 SitemapFileStore::childUrl() 이 sitemapindex 의 에 기록하는 값과 일치해야 한다. // gzip 여부는 manifest 가 결정하므로 .xml / .xml.gz 를 같은 액션에서 처리한다. Route::get('/sitemap.xml', [SitemapController::class, 'index'])->name('web.sitemap'); Route::get('/sitemap-{n}.xml', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child'); Route::get('/sitemap-{n}.xml.gz', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child.gz'); // User 라우트 - user 템플릿 의존성 검증 + SEO 봇 감지 Route::middleware(['template.dependencies:user', 'seo']) ->group(function () { Route::get('/{any?}', function (Request $request) { // 미등록 경로는 SPA 셸 본문 + HTTP 404 (soft 404 방지 — 공개#47). $path = '/'.ltrim($request->getPathInfo(), '/'); if (! app(TemplateRouteResolver::class)->routeExists($path, 'user')) { return response(view('app'), 404); } return view('app'); })->where('any', '(?!admin)(?!api)(?!plugins)(?!.*\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)).*'); });